• wpf企业应用之UI模块解耦


      关于UI模块的解耦,说简单点,首先需要配置菜单与对应操作类的映射关系(或存放于配置文件,或继承接口直接写死在模块代码中,或存放到数据库,原理都一样),然后在菜单加载时,读取配置项动态生成菜单或是其他控件列表,同时为对应菜单项添加点击之类的事件,最后在事件中利用反射生成模块的实例(与界面相关的还需加到父容器中)。

      下面就我写的部分代码做一说明。具体效果见 wpf企业级开发中的几种常见业务场景

      首先界面放置两个容器,一个放菜单,一个放模块UI。其中avalonDock是一个布局容器,可实现类似VS的布局方式。

         

      接下来就是填充菜单,也就是动态生成菜单。

      private void LoadMenu()
      {
          var modules = ModuleHelper.GetModuleInfo();
          var menuitems = BuildMenu(modules);
          menuitems.ForEach(item => ModuleMenu.Items.Add(item));
      }

      先获取菜单,下面是菜单类及部分菜单配置示例。

    public class ModuleInfo
        {
            public ModuleInfo()
            {
            }
    
            public string MenuName
            {
                get;
                set;
            }
            public string MenuName_EN
            {
                get;
                set;
            }
            public string AssemblyFile
            {
                get;
                set;
            }
            public bool CanSetRight
            {
                get;
                set;
            }
            public bool NotUse
            {
                get;
                set;
            }
            public string ClassName
            {
                get;
                set;
            }
    
            public string StartMethod
            {
                get;
                set;
            }
    
            private List<ModuleInfo> _moduleChildren;
            public List<ModuleInfo> ModuleChildren
            {
                get
                {
                    return _moduleChildren ?? (_moduleChildren = new List<ModuleInfo>());
                }
                set
                {
                    _moduleChildren = value;
                }
            }
        }
    View Code

      基本上所有的注入都至少要配置菜单名、类名及操作方法名。

    <Modules>
      <Module MenuName="系统" MenuName_EN="System" CanSetRight="true">
        <Module  MenuName="语言" MenuName_EN="Language">
          <Module  MenuName="中文" MenuName_EN="中文" ClassName="XMIS.Modules.MISSystem.LanguageSetting" StartMethod="SetChinese">
          </Module>
          <Module  MenuName="English" MenuName_EN="English" ClassName="XMIS.Modules.MISSystem.LanguageSetting" StartMethod="SetEnglish">
          </Module>
        </Module>
        <Module  MenuName="操作日志" MenuName_EN="ActionLog" ClassName="XMIS.Modules.MISSystem.ActionLog" CanSetRight="true">
        </Module>
        <Module  MenuName="退出" MenuName_EN="Exit" ClassName="XMIS.ShellForm" StartMethod="Exit">
        </Module>
      </Module>
    
      <Module MenuName="产品" MenuName_EN="Product" CanSetRight="true">
        <Module MenuName="产品类别" MenuName_EN="ProductClassify" ClassName="XMIS.Modules.Product.ProductClassify" CanSetRight="true">
        </Module>
        <Module MenuName="产品列表" MenuName_EN="ProductList" ClassName="XMIS.Modules.Product.ProductList" CanSetRight="true">
        </Module>
        <Module MenuName="产品配料" MenuName_EN="ProductPlan" ClassName="XMIS.Modules.Product.ProductMaterial" CanSetRight="true">
        </Module>
      </Module>
    
    </Modules>
    View Code

       下面的两个方法读取菜单配置生成对应类。

    public class ModuleHelper
        {
            private static List<ModuleInfo> BuildModel(XmlNodeList nodes)
            {
                var result = new List<ModuleInfo>();
                if (nodes == null || nodes.Count == 0)
                    return result;
                foreach (XmlNode node in nodes)
                {
                    if (node.Attributes["NotUse"] != null && node.Attributes["NotUse"].Value == "true")
                        continue;
                    var model = new ModuleInfo();
                    if (node.Attributes["MenuName"] != null)
                        model.MenuName = node.Attributes["MenuName"].Value;
                    if (node.Attributes["MenuName_EN"] != null)
                        model.MenuName_EN = node.Attributes["MenuName_EN"].Value;
                    if (node.Attributes["AssemblyFile"] != null)
                        model.AssemblyFile = node.Attributes["AssemblyFile"].Value;
                    if (node.Attributes["ClassName"] != null)
                        model.ClassName = node.Attributes["ClassName"].Value;
                    if (node.Attributes["StartMethod"] != null)
                        model.StartMethod = node.Attributes["StartMethod"].Value;
                    if (node.Attributes["CanSetRight"] != null)
                        model.CanSetRight = Convert.ToBoolean(node.Attributes["CanSetRight"].Value);
                    model.ModuleChildren.AddRange(BuildModel(node.ChildNodes));
                    result.Add(model);
                }
                return result;
            }
    
            public static List<ModuleInfo> GetModuleInfo()
            {
                if (File.Exists("ModuleConfig.xml"))
                {
                    XmlDocument doc = new XmlDocument();
                    doc.Load("ModuleConfig.xml");
                    var root = doc.DocumentElement;
                    var modules = BuildModel(root.ChildNodes);
                    return modules;
                }
                return null;
            }
    
        }
    View Code

       接下来在界面动态创建菜单项。

    private List<MenuItem> BuildMenu(List<ModuleInfo> modules)
      {
         var menuitems = new List<MenuItem>();
         if (modules == null || modules.Count == 0)
           return menuitems;
         foreach (var module in modules)
         {
             MenuItem menuItem = new MenuItem();
             if (AppSetting.GetValue("language") == "en_us")
                menuItem.Header = module.MenuName_EN;
             else
                menuItem.Header = module.MenuName;
             menuItem.Tag = module;
             bool hasRight = HasModuleRight(module.ClassName);
             if (module.CanSetRight && !hasRight)
                menuItem.IsEnabled = false;
             if (!string.IsNullOrEmpty(module.ClassName))
                menuItem.Click += menuItem_Click;
             var children = BuildMenu(module.ModuleChildren);
             children.ForEach(item => menuItem.Items.Add(item));
             menuitems.Add(menuItem);
         }
         return menuitems;
      }

       菜单创建后,接下来就是加载对应模块了。在菜单点击事件中使用反射调用对应类的对应方法。

    //在此使用反射,根据程序集、类型及方法执行相应操作
      private void menuItem_Click(object sender, RoutedEventArgs e)
      {
          ModuleInfo module = (sender as MenuItem).Tag as ModuleInfo;
          if (string.IsNullOrEmpty(module.AssemblyFile))//本程序集
          {
             LoadModule(module);
             return;
          }
          //以下调用插件
          var assembly = Assembly.Load(module.AssemblyFile);
          if (assembly != null)
          {
            try
            {
              var moduleInstance = assembly.CreateInstance(module.ClassName);
              moduleInstance.GetType().InvokeMember(module.StartMethod, BindingFlags.Default | BindingFlags.InvokeMethod, null, moduleInstance, null);
            }
            catch
            {
               MessageBox.Show(LanguageHelper.GetString("ShellForm_menuItem_Click_Msg1") + module.ClassName);
             }
          }
      } 

       填充UI容器,我这里的一些逻辑实现tab页的添加,同时tab页的标题及可以打开的数量可以在对应模块上进行配置,仅供参考,读者可根据自己实际情况编写逻辑。

    private void LoadModule(ModuleInfo module)
       {
          try
          {
            Object moduleInstance = null;
            var showAttr = Type.GetType(module.ClassName).GetCustomAttribute<ModuleShowAttribute>();
            int tabCount = ModuleManager.GetTabCount(module.ClassName);
            if (module.ClassName == "XMIS.ShellForm")//主窗体
               moduleInstance = this;
            else
            {
               if (showAttr == null || tabCount < showAttr.MaxTabCount)
               {
                 moduleInstance = Activator.CreateInstance(Type.GetType(module.ClassName));
                 ModuleManager.AddModuleTab(module.ClassName);
               }
               else//不再添加该模块标签页,直接激活
               {
                 var activeDoc = ModuleContainer.Children.FirstOrDefault(p => p.Content.GetType().ToString() == module.ClassName);
                 if (activeDoc != null)
                    activeDoc.IsSelected = true;
                 return;
               }
            }
            if (moduleInstance == null)
               return;
            if (string.IsNullOrEmpty(module.StartMethod))//不配置StartMethod,就默认加载为标签页窗体
            {
               var moduleTab = moduleInstance as Control;
               if (moduleTab != null)
               {
                 var tabDoc = new LayoutDocument()
                     {
                        Content = moduleTab,
                        IsSelected = true
                     };
                 if (AppSetting.GetValue("language") == "en_us")
                    tabDoc.Title = showAttr.ModuleName_EN;
                 else
                    tabDoc.Title = showAttr.ModuleName;
                 tabDoc.Closed += tabDoc_Closed;
                 tabDoc.IsSelectedChanged += tabDoc_IsSelectedChanged;
                 ModuleContainer.Children.Add(tabDoc);
                }
             }
             else
             {
                moduleInstance.GetType().InvokeMember(module.StartMethod, BindingFlags.Default | BindingFlags.InvokeMethod, null, moduleInstance, null);
             }
          }
          catch
          {
             MessageBox.Show(LanguageHelper.GetString("ShellForm_LoadModule_Msg1") + module.ClassName);
          }
       }

       到这里,注入容器基本完成了。下面这个类是我写的一个模块信息管理类,用于记录tab页的状况,方便进行一些特殊情况的处理。

    public static class ModuleManager
        {
            private static Dictionary<string, int> _moduleTabDic = new Dictionary<string, int>();
            public static Dictionary<string, int> ModuleTabDic
            {
                get
                {
                    return _moduleTabDic;
                }
                set
                {
                    _moduleTabDic = value;
                }
            }
    
            public static System.Windows.Window ContainerWindow
            {
                get;
                set;
            }
    
            public static void AddModuleTab(string moduleName)
            {
                if (ModuleTabDic.ContainsKey(moduleName))
                    ModuleTabDic[moduleName]++;
                else
                    ModuleTabDic.Add(moduleName, 1);
            }
    
            public static int GetTabCount(string moduleName)
            {
                if (!ModuleTabDic.ContainsKey(moduleName))
                    return 0;
                return ModuleTabDic[moduleName];
            }
    
            public static void RemoveModuleTab(string moduleName)
            {
                if (ModuleTabDic.ContainsKey(moduleName) && ModuleTabDic[moduleName] > 0)
                {
                    ModuleTabDic[moduleName]--;
                }
            }
    
            public static void RemoveAllTab()
            {
                ModuleTabDic.Clear();
            }
    
            private static MenuItem FindMenuItem(ItemCollection menuItems, string menuName)
            {
                foreach (var item in menuItems)//后根遍历搜索,之查找叶子节点,配置菜单时,菜单叶子节点名尽量不要重复
                {
                    var menuitem = (MenuItem)item;
                    var temitem = FindMenuItem(menuitem.Items, menuName);
                    if (temitem != null)
                        return temitem;
                    if (menuitem.Items.Count == 0 && ((ModuleInfo)menuitem.Tag).MenuName == menuName)
                        return menuitem;
                }
                return null;
            }
    
            public static void SetMenuCheckState(string menuName, bool isChecked)
            {
                Menu ModuleMenu = (Menu)ContainerWindow.FindName("ModuleMenu");
                var menuItem = FindMenuItem(ModuleMenu.Items, menuName);
                if (menuItem != null)
                    menuItem.IsChecked = isChecked;
            }
    
            private static void SetMenuLanguage(ItemCollection menuItems, string language)
            {
                foreach (var item in menuItems)
                {
                    var menuitem = (MenuItem)item;
                    if (language == "en_us")
                        menuitem.Header = ((ModuleInfo)menuitem.Tag).MenuName_EN;
                    else
                        menuitem.Header = ((ModuleInfo)menuitem.Tag).MenuName;
                    SetMenuLanguage(menuitem.Items, language);
                }
            }
    
            public static void SetMenuLanguage(string language)
            {
                Menu ModuleMenu = (Menu)ContainerWindow.FindName("ModuleMenu");
                SetMenuLanguage(ModuleMenu.Items, language);
            }
    
            public static void SetDocTitleLanguage(string language)
            {
                foreach (var module in ModuleTabDic.Keys)
                {
                    var showAttribute = Type.GetType(module).GetCustomAttribute<ModuleShowAttribute>();
                    LayoutDocumentPane ModuleContainer = (LayoutDocumentPane)ContainerWindow.FindName("ModuleContainer");
                    var tab = ModuleContainer.Children.FirstOrDefault(m => m.Content.GetType().ToString() == module);
                    if (tab == null)
                        continue;
                    if (language == "en_us")
                        tab.Title = showAttribute.ModuleName_EN;
                    else
                        tab.Title = showAttribute.ModuleName;
                }
            }
    
            public static void SetStatusMessage(string message)
            {
                TextBlock Text_StatusMessage = (TextBlock)ContainerWindow.FindName("Text_StatusMessage");
                Text_StatusMessage.Text = message;
            }
        }
    View Code
  • 相关阅读:
    no copy constructor available or copy constructor is declared &#39;explicit&#39;
    Jetty:配置连接器
    DataTable数据转换为实体
    iOS 单元測试之XCTest具体解释(一)
    UDP C/S编程
    xml的加密和解密
    lua 加密
    shell脚本加密
    实现业务系统中的用户权限管理--设计篇
    通用用户权限系统设计(数据库设计)
  • 原文地址:https://www.cnblogs.com/Fuss/p/4090632.html
Copyright © 2020-2023  润新知