一步一步学习开发BPM工作流系统--------(三)开发WinForm的应用平台-2

    上篇介绍了如何利用反射实现动态调用Dll,学会这个技术我们就不怕软件大而臃肿了,无论多复杂的系统我们可以把它们模块化,技术简单用处很大。然而Demo是简单的应用是复杂的,我们的WinForm应用平台要考虑的事情很多,不但要考虑功能还要考虑容错性。下面几个问题是我们首先要解决的:

   1、Dll配置信息的存放,是放在配置文件中还是数据库中?

   2、配置Dll的时候能否通过选择Dll文件的方式来完成,类名输入要求比较精确,手动输入容易出错,能否提供选择输入的方式?

   3、Dll可能由水平层次不一的人来编写,很难保证谁的Dll不会出现致命的错误,有的程序员做了友好处理,有的没有,如何避免弹出.Net自带的错误对话框,能否提供一个统一的错误捕获机制,来处理错误提示?

  4、Dll的窗体打开方式,是鼠标单击执行还是双击执行,是在新窗口中打开还是在原有窗口中打开,是模态还是非模态,如何避免重复?

  解决这四个问题我们的应用平台雏形基本可以完成了。本篇我们一一解决这些问题。

问题1、Dll配置信息的存放

作为大型的应用系统最好使用数据库来管理数据,所以我们把Dll的配置信息存放到数据库中。在数据库中建一张配置表。

DllFileNameDll文件名,默认放到主程序目录下下
DllClassName引用的类名,带命名空间
FormName窗体的名称
BlankWindows是否在新窗口中打开
MouseIsClick是否是单击执行,否则双击执行
SDIWindowsSDI类型的窗体是否模态显示
DllMethodName方法名,调用窗体就不调用方法,这里只能一个是有效的
NodeType应用平台左侧是导航树,需要指明节点调用的是窗体还是功能

下一步建一个Dll配置的界面,如下图:

image

问题2、选择输入类信息

如果手动输入dll文件名或者类名很容易出错,我们提供一种选择输入的方式,基本上从软件设计角度来讲容易出错的信息都通过选择来输入。这里我们用的技术还是反射调用,从程序集中取出类名和方法名,.Net提供了这样的方法,可以取到所有的类信息和方法,不管是静态方法还是动态方法。

我们把上一篇的例子稍微改动一下,在TestDll中增加一个窗体Form2,这个窗体是一个内部窗体不需要对外调用的,Form1是需要外部调用的。下面的代码可以把TestDll.dll中的类取出来。

/// <summary>
        /// 获取程序集中的类信息
        /// </summary>
        /// <param name="AssemblyFileName">全路径的程序集文件名称</param>
        /// <returns>类列表</returns>
        public static List<string> GetAssemblyClassList(string AssemblyFileName)
        {
            List<string> result = new List<string>();
            if (File.Exists(AssemblyFileName) == false)
            {
                return result;
            }
            Assembly myAss = Assembly.LoadFrom(AssemblyFileName);  //加载程序集文件
            Type[] types = myAss.GetTypes();//得到程序集中的所有类型
            foreach (Type each in types)
            {
                if (each.IsClass == false) continue;//判断是否是类,如果不是类过滤掉
                result.Add(each.FullName);

            }
            return result;
        }

运行后得到类信息,如下图:

image

下面的代码返回对应类中的方法名:

/// <summary>
        /// 获取程序集中对应类的方法
        /// </summary>
        /// <param name="AssemblyFileName">全路径的程序集文件名称</param>
        /// <param name="className">类的全名</param>
        /// <returns>方法名列表</returns>
        public static List<string> GetAssemblyMethodList(string AssemblyFileName, string className)
        {
            List<string> result = new List<string>();
            if (File.Exists(AssemblyFileName) == false)
            {
                return result;
            }
            Assembly myAss = Assembly.LoadFrom(AssemblyFileName);  //获取程序集显示名称  
            Type[] types = myAss.GetTypes();
            foreach (Type each in types)
            {
                if (each.IsClass == false) continue;
                if (each.FullName == className)
                {
                    MethodInfo[] myMethods = each.GetMethods();
                    foreach (MethodInfo m in myMethods)
                    {
                        if (m.IsPublic)//如果是公共方法
                        result.Add(m.Name);
                    }
                }

            }
            return result;

        }

运行后得到类中的方法,如下图:

image

现在可以实现选择输入了,是不是功能提高了不少。但是问题又出来了,我们看到有很多我们不想看到的类名和方法名,Net提供的过滤手段比较有限,我试了几个判断方法,是否是public,好像public的方法也很多,包括基类的都显示出来了,虽然可以过滤掉父类的方法,但是也不能明确的显示我们指定的。有没有办法只显示我们指定输出的类名和方法呢?这样我们就不会在那么多类型中查找了,只要思想不滑坡,办法总比困难多。我们可以利用自定义属性,在要输出的类或者方法上增加一个自定义属性,在获取这些信息的时候判断是否有自定义属性,如果有就输出如果没有就不输出,这样不就可以了吗。我们来试试看,首先定义一个输出的自定义属性,代码如下:

public class OutMethodsAttribute : Attribute
    {
        protected string _name;

        public string Name
        {
            get
            {
                return this._name;
            }
        }
        public OutMethodsAttribute(string name)
        {
            _name = name;
        }
    }

然后在要输出类上加上这个自定义属性。把Form1窗体代码修改如下:

namespace TestDll
{
    [OutMethods("Form1")]
    public partial class Form1 : Form
    {
        string _userName = "";
        public Form1()
        {
            InitializeComponent();
        }
        public Form1(string userName)
        {
            InitializeComponent();
            _userName = userName;
            label1.Text = _userName;
        }
        private void button1_Click(object sender, EventArgs e)
        {
            MessageBox.Show(_userName ",调用Ok了!");
        }
        [OutMethods("Add")]
        public int Add(int a, int b)
        {
            return a   b;
        }
    }
}

我们再来修改获取类信息的方法和获取方法的代码,代码如下:

/// <summary>
        /// 获取程序集中的类信息
        /// </summary>
        /// <param name="AssemblyFileName">全路径的程序集文件名称</param>
        /// <returns>类列表</returns>
        public static List<string> GetAssemblyClassList(string AssemblyFileName)
        {
            List<string> result = new List<string>();
            if (File.Exists(AssemblyFileName) == false)
            {
                return result;
            }
            Assembly myAss = Assembly.LoadFrom(AssemblyFileName);  //加载程序集文件
            Type[] types = myAss.GetTypes();//得到程序集中的所有类型
            object[] objClass = null;
            foreach (Type each in types)
            {
                if (each.IsClass == false) continue;//判断是否是类,如果不是类过滤掉
                objClass = each.GetCustomAttributes(true);//获取自定属性
                foreach (Attribute clAttribute in objClass)
                {
                    if (clAttribute is OutMethodsAttribute)//如果有输出标记
                    {
                        result.Add(each.FullName);
                    }
                }
            }
            return result;
        }
        /// <summary>
        /// 获取程序集中有输出标记类的方法
        /// </summary>
        /// <param name="AssemblyFileName">全路径的程序集文件名称</param>
        /// <param name="className">类的全名</param>
        /// <returns>方法名列表</returns>
        public static List<string> GetAssemblyMethodList(string AssemblyFileName, string className)
        {

            OutMethodsAttribute outAttribute;
            List<string> result = new List<string>();
            if (File.Exists(AssemblyFileName) == false)
            {
                return result;
            }
            Assembly myAss = Assembly.LoadFrom(AssemblyFileName);  //获取程序集显示名称  
            Type[] types = myAss.GetTypes();//得到程序集中的所有类型
            object[] objMethod = null;
            foreach (Type each in types)
            {
                if (each.IsClass == false) continue;
                if (each.FullName == className)
                {
                    MethodInfo[] myMethods = each.GetMethods();
                    foreach (MethodInfo m in myMethods)
                    {
                        objMethod = m.GetCustomAttributes(true);//获取自定属性
                        foreach (Attribute attr in objMethod)
                        {
                            if (attr is OutMethodsAttribute)//如果有输出标记
                            {
                                outAttribute = (attr as OutMethodsAttribute);
                                result.Add(outAttribute.Name);
                            }
                        }
                    }
                }

            }
            return result;
        }

运行修改后的代码,果然只显示了我们定义输出标志的信息,如下图:

image

这样就太完美了,这是我想到的过滤方式,不知道大家有没有更好的方法。

问题3、应用平台统一的错误捕获机制

我们先来让Test.dll触发一个异常,我们不做任何处理。下面的代码会抛出一个异常。

private void button2_Click(object sender, EventArgs e)
        {
            throw new Exception("触发一个异常!");
        }

弹出的界面如下图:

image

这种界面是很不友好的,如果一个系统运行中弹出这样的界面可以说是很大的失败。现实中每个模块是有不同的程序员开发的,有的人代码习惯很好,做了完善的错误处理,有的人则不重视,所以很难保证抛出这种异常,那么我们作为一个平台怎么办呢。我们能否接管抛出的所有异常,由平台统一处理?还是那句话只要思想不滑坡办法总比困难多。Winform的应用程序可以提供一个单独的线程来捕获应用程序的所有异常,我们可以利用这个线程来处理。下面的代码就可以完成统一的异常处理:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Threading;
namespace WinAppDynamicDemo
{
    static class Program
    {
        /// <summary>
        /// 应用程序的主入口点。
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            ThreadExceptionHandler handler = new ThreadExceptionHandler();//捕获异常的线程
            Application.ThreadException  = new ThreadExceptionEventHandler(handler.Application_ThreadException);//为应用程序指定捕获异常的线程
            Application.Run(new Form1());
        }

    }
    /// 
    /// 一个处理异常的线程类
    /// 
    internal class ThreadExceptionHandler
    {

        public void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
        {
            try
            {
                DialogResult result = ShowThreadExceptionDialog(e.Exception);
                if (result == DialogResult.Abort)
                    Application.Exit();
            }
            catch (Exception ex)
            {
                string errorStr = "未知错误!"   ex.Message.ToString();
                try
                {
                    MessageBox.Show(errorStr, "出错了");
                }
                finally
                {
                    Application.Exit();
                }

            }
        }

        /// 
        /// 调用一个统一的错误处理窗口
        /// 
        private DialogResult ShowThreadExceptionDialog(Exception ex)
        {
            string InnerError = "";//前一个异常,便于进步一查找错误原因
            if (ex.InnerException != null)
            {
                InnerError = ex.InnerException.Message;
            }
            else
            {
                InnerError = ex.Message;
            }
            string errorMessage = "错误信息:\n"  
                ex.Message  
                "\n错误原因:\n"   InnerError  
                 "\n错误来源:\n"  
                 ex.Source  
                "\n产生错误的方法:\n"  
                ex.TargetSite  
                "\n应用程序运行时类型:\n"  
                ex.GetType();
            fmShowError infoform = new fmShowError(errorMessage);
            return infoform.ShowDialog();

        }
    }
}

再次执行触发异常的代码,我们发现所有的异常都被我们统一的错误处理线程接管了,界面如下图:

image

这个功能有点类似WebForm中统一的错误处理页面。这里处理的不够美观,我们再把他处理的好看一些。下图是HF2.0中的错误处理截图:

image

问题4、窗体打开方式

对MDI类型的窗体我们防止重复打开,可以利用主窗体的MdiChildren属性,遍历所有打开的子窗体看是否已经存在,如果已经存在那么显示到最前面。检查窗体是否存在代码如下:

/// <summary>
        /// 根据子窗体名字判断窗体是否打开
        /// </summary>
        /// <param name="ParentForm">主窗体</param>
        /// <param name="formtitle">子窗体的标题</param>
        /// <returns>窗体</returns>
        private System.Windows.Forms.Form FormExists(System.Windows.Forms.Form ParentForm,string formname)
        {
            System.Windows.Forms.Form bl=null;
            foreach(Form objForm in ParentForm.MdiChildren)
            {
                if (objForm.Name.Equals(formname))
                {
                    bl = objForm;
                    break;
                }
            }
            return bl;
        }

至于单击打开还是双击执行,我们只要把代码放在鼠标单击事件或是双击事件里面就可以,这里不赘述了。

到此为止,我们的应用平台基本很完善了。下面有本篇demo的源码,这些代码是实现的核心,并不是一个完整的应用,读者可以自己去完善它。在该系列文章的结束我会提供一个完整的应用下载,HF1.0学习版的下载,这是一个完整的工作流应用平台,包含平台涉及到的所有部分。这里我们还是一步一步来,下一篇我们介绍工作流平台数据库访问层,这不是一个普通的访问层,也许你自己也写过数据库访问层,但是我们提供的这个不但支持多数据库,而且支持本地和远程访问,远程不是简单的链接远程数据库,而是通过wcf的方式链接远程数据库,而且我们使用了数据代理层,这样业务模块的丝毫不用变化,只要更换代理层就可以实现本地和远程访问的切换!

本篇demo下载地址: http://files.cnblogs.com/legweifang/WinAppDynamicDemo20120725.rar

 

转载于:https://www.cnblogs.com/legweifang/archive/2012/07/25/WinForm%e5%ba%94%e7%94%a8%e5%b9%b3%e5%8f%b0.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值