代码改变世界

基于Attribute(属性)的插件开发

2012-04-23 09:51  yangtam  阅读(380)  评论(0)    收藏  举报

    在Windows Form中我们常常遇到这样的一个问题,系统升级后菜单就变了,或者将一个dll放入到对应运行的文件夹下面,然后重新运行系统,系统菜单就自然改变了。类似这样的解决方法会有很多,下面只是个人的想法,有什么不对的地方请各位指出。

    在.NET平台下主要通过Attribute+反射来实现的,具体的步骤如下:

  1. 先定义一个Attribute,比如说(MenuItemAttribute);
  2. 根据不同的需求继承MenuItemAttribute;
  3. 定义并实现一个操作反射的类(AssemblyHelper),主要用于查找MenuItemAttribute;
  4. 在集成的MainForm中通过调用AssemblyHelper来完成菜单的生成;

    MenuItemAttribute主要是一个抽象类,其定义如下:

View Code
 public abstract class MenuItemAttribute : Attribute
    {
        protected MenuItemAttribute(string text)
        {
            Text = text;
        }

        public string Text { get; set; }

        public int Id { get; set; }

        public int ParentId { get; set; }

        public virtual void Excute(Type targetType)
        {
            if(targetType.IsSubclassOf(typeof(Form)))
            {
                ((Form) Activator.CreateInstance(targetType)).Show();
            }
            if (targetType.IsSubclassOf(typeof(Window)))
            {
                ((Window)Activator.CreateInstance(targetType)).Show();
            }
        }
    }

 

  Text表示菜单显示的文字,而Id主要是用来判断该MenuItem在菜单中出现的顺序,而ParentId表示该菜单属于哪一级菜单的子菜单。方法Excute(Type targetType)主要是用来保存当点击了MenuItem的时候触发的事件做的事情。

    定义一个ToolStripMenuItemAttribute继承MenuItemAttribute,如下:

View Code
 [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
    public class ToolStripMenuItemAttribute : MenuItemAttribute
    {
        public ToolStripMenuItemAttribute(string text)
            : base(text)
        {
        }
    }

    定义一个操作反射的类AssemblyHelper,该类主要是通过使用反射查找MenuItemAttribute来实现对菜单的动态生成,定义如下:

   

View Code
 public class AssemblyHelper
    {
        public static ToolStripItem[] GetMenuItems()
        {
            var result = new List<ToolStripItem>();
            string[] files = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll",
                                                SearchOption.TopDirectoryOnly);
            foreach (string t in files)
            {
                var assembly = Assembly.LoadFrom(t);
                var types = assembly.GetTypes();
                var nodes = new List<Node>();
                foreach (var type in types)
                {
                    var attrs = (MenuItemAttribute[]) type.GetCustomAttributes(typeof (MenuItemAttribute), true);
                    if (attrs.Length > 0)
                    {
                        nodes.AddRange(attrs.Select(attr => new Node() {MenuItem = attr, Target = type}));
                    }
                }
                if (nodes.Count > 0)
                {
                    var gNodes = nodes.GroupBy(p => p.MenuItem.ParentId).OrderBy(p => p.Key).ToList();
                    var root = gNodes[0].First();
                    var rootMenu = Order(root, gNodes);
                    result.Add(rootMenu);
                }
            }
            return result.ToArray();
        }

        public static ToolStripMenuItem Order(Node root, List<IGrouping<int, Node>> nodes)
        {
            var menuItem = new ToolStripMenuItem(root.MenuItem.Text);
            var nodes2 = nodes.Find(p => p.Key == root.MenuItem.Id);
            if(nodes2 == null)
            {
                menuItem.Click += (sender, e) => root.MenuItem.Excute(root.Target);
            }
            else
            {
                var temNode2 = nodes2.OrderBy(p => p.MenuItem.Id).ToList();
                foreach (var node in temNode2)
                {
                    menuItem.DropDownItems.Add(Order(node, nodes));
                }
            }
            return menuItem;
        }
    }

    public class Node
    {
        public MenuItemAttribute MenuItem { get; set; }

        public Type Target { get; set; }

    }

    在实现该功能中主要采用了遍历书的思想来实现的,首先定义一个节点类型Node。该类主要是用来保存MenuItemAttribute和对应的Form类型的。在AssemblyHelper中主要是先通过分组的方式生成一个树,然后在通过Order遍历数(先序遍历),来生成对应的菜单。

    运行效果如下图: