消息字段的动态发现






4.67/5 (2投票s)
使用 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
”。 此处的目的是仅在显示屏上显示 DataField1
和 DataField2
。 noneDisplayData
用于 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 文件的链接