Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Dynamic Discovery of Message Fields

0.00/5 (No votes)
7 Jul 2014 1  
Utilize System.Attribute and Assembly to discover type and retrieve data members to be displayed on windows form

Introduction

This project is to programmatically discover data members from custom message classes and display the value of each data member. Suppose I am writing an application to receive data messages from a remote connection and display the value of each data field within each message. With the assumption that the remote connection does not support object serialization. To do so I need to define a class for each message type and its data members as well as a windows form for each. To generalize the problem I can write a generic form that programatically discovers all data fields of a message class and programmatically construct the form controls to display the value of each data field.

Background

Programmatically discovery of message fields eliminiates much hand editting of windows form. Imagine there are hundreds of different messages and you need one windows form for each message it will take a long time to finish up.

To simplify the problem the remote connection is omitted. Only one message and one form is defined in this project. Once a program paradigm is established adding more messages and forms will be easy done by following the same concept.

Project DynamicForm

Create a new project from Visual C# namely DynamicForm. Select Windows Form Project.

MessageFieldAttribute

Define a custome attribute "MessageFieldAttribute" and use it for data fields that you want to display. The MessageFieldAttribute only has one property "Description" however it can be expanded to include other properties to let you have informaiton about the data field such as data format and size. For simplicity of this project only description property is provided.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DynamicForm.Messages
{
    public sealed class MessageFieldAttr : System.Attribute
    {
    
        public string Description { get; set; }
                
        public MessageFieldAttr()
        {
            Description = "Message Field Description";
        }

    }
}

Messages

Add a class ClientDataMessage1 that has 2 data fields to be displayed namely "DataField1" and "DataField2" and non displayable members "noneDisplayData".  The displable data field has custom attribute "MessageFieldAttr". The intent here is to only show DataField1 and DataField2 on the display. noneDisplayData is meant for book keeping purpose of ClientDataMessage1. It is declared here for the sake of non displayable demonstration purpose.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DynamicForm.Messages
{
    public class ClientDataMessage1
    {
        [MessageFieldAttr(Description = "DataField1")]        
        public uint DataField1;

        [MessageFieldAttr(Description = "DataField2")]
        public double DataField2;
        //not to be displayed
        private int noneDisplayData;
        public string[] ToString()
        {
            string[] s = new string[2];

            s[0] = DataField1.ToString();
            
            s[1] = DataField2.ToString();

            return s;
        }
    }
}

member method ToString return the values of displayable data fields in the order they are declared in ClientDataMessage1 class.

Add MessageDiscovery class to the project, MessageDiscovery class takes in the class name of the message of which data fields will be displayed in a form. MessageDiscovery retreives the Type of this message then retrieves any data fields that has MessageFieldAttr custom attribute. It stores the data fields in a List<MemberInfo>. The Module name is "Dynamicform.exe" which is the name of the project (DynamicForm).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace DynamicForm
{
    class MessageDiscovery
    {
 
        public List<MemberInfo> DataFields { get; set; }
    
       public MessageDiscovery(string className)
        {
            DataFields = new List<MemberInfo>();
    
            Module myModule = typeof(MessageDiscovery).Assembly.GetModule("DynamicForm.exe");
             
            //discover all types in my module with ClientDataMessage prefix. Assume all incoming messages are defined a class with
            //naming convention ClientDataMessageX.cs where X can be anything

            Type[] myTypes = myModule.FindTypes(Module.FilterTypeNameIgnoreCase, className);

            foreach (Type type in myTypes)
            {                
                MemberInfo[] members = type.GetMembers();
                foreach (MemberInfo member in members)
                {                                        
                    object[] attrs = member.GetCustomAttributes(false);
                    foreach (object obj in attrs)
                    {
                        if (string.Compare(obj.ToString(), "DynamicForm.Messages.MessageFieldAttr") == 0)
                        {
                            DataFields.Add(member);
                        }
                    }
                }
            }
        }
    }
}

Using the Code

Add a form to the project namely Form1.  The method constructDataFields creates columns of labels. Column 1 shows data field name and column 2 (dataFieldLabels) shows data field value.

The method ShowData takes in the message object and set the text of column 2 labels (dataFieldLabels)

to the values of data fields from the message object. To test it out I instantiate a ClientDataMessage1 in the constructor Form1() and call ShowData(msg) to demonstrate that it works.

This form is generic enough that when the message ClientDataMessage1 changes the form source does not need to be updated. it will still work.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Reflection;

namespace DynamicForm
{
    public partial class Form1 : Form
    {

        private Dictionary<string,Label> dataFieldLabels;
        
        private MessageDiscovery messageD = new MessageDiscovery("ClientDataMessage1");

        public Form1()
        {
            InitializeComponent();
            constructDataFields();

            Messages.ClientDataMessage1 msg = new Messages.ClientDataMessage1();

            //test out the showData method
            msg.DataField1 = 1;
            msg.DataField2 = 2;
            ShowData(msg);
        }

        private void constructDataFields()
        {
            Label[] l = new Label[messageD.DataFields.Count];
            dataFieldLabels = new Dictionary<string, Label>();
  
            Console.WriteLine(messageD.DataFields.Count);
            for (int i = 0; i < messageD.DataFields.Count; i++)
            {
                l[i] = new Label();
                l[i].Text = messageD.DataFields[i].Name;

                l[i].Size = new System.Drawing.Size(100, 25);
                l[i].Location = new System.Drawing.Point(25, 25 + i * 25 + 5);
                Console.WriteLine("new Label {0} {1} ", messageD.DataFields[i].Name, l[i].Text);

                dataFieldLabels[messageD.DataFields[i].Name] = new Label();
                dataFieldLabels[messageD.DataFields[i].Name].Size = new System.Drawing.Size(100, 25);
                dataFieldLabels[messageD.DataFields[i].Name].Location = new System.Drawing.Point(200, 25 + i * 25 + 5);
                dataFieldLabels[messageD.DataFields[i].Name].Text = "N/A";
                this.Controls.Add(dataFieldLabels[messageD.DataFields[i].Name]);
            }
            this.Controls.AddRange(l);            
        }

        public void ShowData(Messages.ClientDataMessage1 msg)
        {
            string[] values = msg.ToString();
            for (int i = 0; i < values.Length; i++)
            {
                dataFieldLabels.ElementAt(i).Value.Text = values[i];
            }

        }
    }

}

Points of Interest

Imagine that there are 20 data fields in each message and you dont have to manually edit the form to display each data field? how about there are 100 messages each has 20 or more data fields? one can further generalize the Form1 such that it takes in a parameter "class name" and construct the form according the class of interest? that is up for my next challenge or yours. Happy coding.

 

Revision History

Added links to source zip files

 

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here