65.9K
CodeProject 正在变化。 阅读更多。
Home

消息字段的动态发现

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (2投票s)

2014年7月2日

CPOL

3分钟阅读

viewsIcon

15869

downloadIcon

170

使用 System.Attribute 和 Assembly 来发现类型并检索要在 Windows 窗体上显示的数据成员

引言

这个项目的目的是以编程方式发现自定义消息类中的数据成员,并显示每个数据成员的值。假设我正在编写一个应用程序,用于接收来自远程连接的数据消息,并显示每个消息中每个数据字段的值。 假设远程连接不支持对象序列化。 为此,我需要为每种消息类型及其数据成员定义一个类,以及为每个消息定义一个 Windows 窗体。 为了概括这个问题,我可以编写一个通用窗体,该窗体以编程方式发现消息类的所有数据字段,并以编程方式构建窗体控件以显示每个数据字段的值。

背景

以编程方式发现消息字段可以减少大量手动编辑 Windows 窗体的工作。 想象一下,有数百种不同的消息,并且每条消息都需要一个 Windows 窗体,这将花费很长时间才能完成。

为了简化问题,省略了远程连接。 在此项目中仅定义了一条消息和一个窗体。 一旦建立了程序范例,通过遵循相同的概念,添加更多的消息和窗体将很容易完成。

项目 DynamicForm

从 Visual C# 创建一个新项目,名为 DynamicForm。 选择 Windows 窗体项目。

MessageFieldAttribute

定义一个自定义属性“MessageFieldAttribute”,并将其用于要显示的数据字段。 MessageFieldAttribute 只有一个属性“Description”,但是可以扩展它以包含其他属性,以便您可以获得有关数据字段的信息,例如数据格式和大小。 为了简化此项目,仅提供了 description 属性。

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";
        }

    }
}

消息

添加一个类 ClientDataMessage1,它具有 2 个要显示的数据字段,即“DataField1”和“DataField2”以及不可显示成员“noneDisplayData”。 可显示数据字段具有自定义属性“MessageFieldAttr”。 此处的目的是仅在显示屏上显示 DataField1DataField2noneDisplayData 用于 ClientDataMessage1 的簿记目的。 在此处声明它是为了进行不可显示演示。

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;
        }
    }
}

成员方法 ToString 返回可显示数据字段的值,顺序与它们在 ClientDataMessage1 类中声明的顺序相同。

MessageDiscovery 类添加到项目中,MessageDiscovery 类接受要在一个窗体中显示其数据字段的消息的类名。 MessageDiscovery 检索此消息的类型,然后检索具有 MessageFieldAttr 自定义属性的任何数据字段。 它将数据字段存储在 List<MemberInfo> 中。 模块名称为“Dynamicform.exe”,它是项目 (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

向项目添加一个窗体,即 Form1。 方法 constructDataFields 创建标签列。 第 1 列显示数据字段名称,第 2 列(dataFieldLabels)显示数据字段值。

方法 ShowData 接收消息对象,并设置第 2 列标签 (dataFieldLabels) 的文本

设置为来自消息对象的数据字段的值。 为了进行测试,我在构造函数 Form1() 中实例化了一个 ClientDataMessage1 并调用 ShowData(msg) 以演示其工作原理。

此窗体足够通用,当消息 ClientDataMessage1 更改时,无需更新窗体源代码。 它仍然可以工作。

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];
            }

        }
    }

}

关注点

想象一下,每条消息中有 20 个数据字段,您不必手动编辑表单来显示每个数据字段? 如果有 100 条消息,每条消息有 20 个或更多数据字段呢? 可以进一步概括 Form1,使其接受参数“类名”并根据感兴趣的类构造窗体? 这取决于我的下一个挑战或你的挑战。 祝你编码愉快。

 

修订历史

添加了指向源 zip 文件的链接

 

© . All rights reserved.