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

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

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

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

public class ModuleHelper
{
private static List<ModuleInfo> BuildModel(XmlNodeList nodes)
{
var result = new List<ModuleInfo>();
if (nodes == null || nodes.Count == )
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;
} }

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

private List<MenuItem> BuildMenu(List<ModuleInfo> modules)
{
var menuitems = new List<MenuItem>();
if (modules == null || modules.Count == )
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, );
} public static int GetTabCount(string moduleName)
{
if (!ModuleTabDic.ContainsKey(moduleName))
return ;
return ModuleTabDic[moduleName];
} public static void RemoveModuleTab(string moduleName)
{
if (ModuleTabDic.ContainsKey(moduleName) && ModuleTabDic[moduleName] > )
{
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 == && ((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;
}
}

wpf企业应用之UI模块解耦的更多相关文章

  1. 使用WPF来创建 Metro UI程序

    本文转载:http://www.cnblogs.com/TianFang/p/3184211.html 这个是我以前网上看到的一篇文章,原文地址是:Building a Metro UI with W ...

  2. Spring.NET在MVC中实现业务层和UI层解耦

    最近在项目中用到了Spring.NET,使用它来实现业务层和UI层解耦.使用过程中难免遇到问题,现把遇到的一些问题整理出来,留作笔记. 使用的开发工具是vs2017,.netframework 4.6 ...

  3. WPF 支持的多线程 UI 并不是线程安全的

    原文:WPF 支持的多线程 UI 并不是线程安全的 版权声明:本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可.欢迎转载.使用.重新发布,但务必保留文章署名吕毅(包含链 ...

  4. WPF 精修篇 非UI进程后台更新UI进程

    原文:WPF 精修篇 非UI进程后台更新UI进程 <Grid> <Grid.RowDefinitions> <RowDefinition Height="11* ...

  5. 示例:自定义WPF底层控件UI库 HeBianGu.General.WpfControlLib V2.0版本

    原文:示例:自定义WPF底层控件UI库 HeBianGu.General.WpfControlLib V2.0版本 一.目的:封装了一些控件到自定义的控件库中,方便快速开发 二.实现功能: 基本实现常 ...

  6. WPF子线程更新UI(Dispatcher.BeginInvoke)

       在做WPF开发时,如果直接在子线程里更新UI会报错—–“调用线程无法访问此对象,因为另一个线程拥有该对象.”,这是因为WPF禁止在非UI线程里直接更新UI界面. 解决方案:   在子线程里调用D ...

  7. [WPF]WPF Data Virtualization和UI Virtualization

    这篇博客将介绍WPF中的虚拟化技术. 1. Data Virtualization 通常情况下我们说数据虚拟化是指数据源没有完全加载,仅加载当前需要显示的数据呈现给用户.这种场景会让我们想到数据分页显 ...

  8. 富客户端 wpf, Winform 多线程更新UI控件

    前言 在富客户端的app中,如果在主线程中运行一些长时间的任务,那么应用程序的UI就不能正常相应.因为主线程要负责消息循环,相应鼠标等事件还有展现UI. 因此我们可以开启一个线程来格外处理需要长时间的 ...

  9. C# WPF 使用委托修改UI控件

    近段时间在自学WPF,是一个完全不懂WPF的菜鸟,对于在线程中修改UI控件使用委托做一个记录,给自已以后查询也给需要的参考: 界面只放一个RichTextBox,在窗体启动时开起两个线程,调用两个函数 ...

随机推荐

  1. 常用的20个强大的 Sublime Text 插件

    作为一个开发者你不可能没听说过 Sublime Text.不过你没听说过也没关系,下面让你明白. Sublime Text是一款非常精巧的文本编辑器,适合编写代码.做笔记.写文章.它用户界面十分整洁, ...

  2. javascript小技巧之with()方法

    With()方法平时用得不多,本文用个小例子来学习一下.在这里记录.个人感觉还是很方便的. 有了 With 语句,在存取对象属性和方法时就不用重复指定参考对象,在 With 语句块中,凡是 JavaS ...

  3. 【leetcode 简单】第十九题 删除排序链表中的重复元素

    给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次. 示例 1: 输入: 1->1->2 输出: 1->2 示例 2: 输入: 1->1->2->3-&g ...

  4. mysql-front导入数据失败:“在多字节的目标代码页中,没有此 Unicode 字符可以映射到的字符”

    mysql-front导入sql文件失败,弹出框显示如下: 解决方法:在选择文件时,选择合适的字符集即可 参考:http://www.th7.cn/db/mysql/201604/185149.sht ...

  5. PHP数据库类

    简单封装PHP操作MySQL的类 <?php /* 类的名称:Model 类的作用:连接数据库执行sql语句 作 者:lim 更新时间:20170812 */ class Model{ //存放 ...

  6. Java多线程学习(七)并发编程中一些问题

    本节思维导图: 关注微信公众号:"Java面试通关手册" 回复"Java多线程"获取思维导图源文件和思维导图软件. 多线程就一定好吗?快吗?? 并发编程的目的就 ...

  7. python【项目】:基于socket的FTP服务器

    功能要求 1. 用户加密认证 2. 服务端采用 SocketServer实现,支持多客户端连接 3. 每个用户有自己的家目录且只能访问自己的家目录 4. 对用户进行磁盘配额.不同用户配额可不同 5. ...

  8. 湖南省第六届省赛题 Biggest Number (dfs+bfs,好题)

    Biggest Number 时间限制:1000 ms  |  内存限制:65535 KB 难度:4 描述 You have a maze with obstacles and non-zero di ...

  9. PHP任意文件上传漏洞CVE-2015-2348浅析

    昨晚安全新闻爆出一个“PHP任意文件上传漏洞”,CVE编号为:CVE-2015-2348. 当时楼主正准备收拾东西回家,看到这个新闻心里一惊:失传江湖多年的0字符截断上传漏洞又重现了?而且还影响这么多 ...

  10. Spring学习-理解IOC和依赖注入

    最近刚买了一本介绍ssm框架的书,里面主要对Mybatis.spring.springmvc和redis做了很多的讲解,个人觉得虽然有的内容我看不懂,但是整体上还是不错的.最近正在学习中,一边学习一边 ...