博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
WinForm窗体的创建于传值(1)
阅读量:5776 次
发布时间:2019-06-18

本文共 22888 字,大约阅读时间需要 76 分钟。

一.  前言

自己很少做WinForm方面的开发,最近公司的一个大型项目中,客户要求Client使用WinForm,Server使用java。以前对WinForm的认识并不深入,在这次开发过程中,陆续接触到了一些WinForm方面的技术。其中,今天要跟大家讨论的就是关于WinForm窗体的创建以及传值方面的东西。

 

二.  概述

以前认为WinForm的程序一般都是编译为.exe,程序中的窗体直接new出来,然后根据要求,进行show或者showDialog。但是,在公司目前的这个项目中,所有的模块都编译为.dll(不管是不是窗体),然后通过反射进行创建以及show。不清楚大家在自己的项目中是否这样使用。在此,分享出来与大家一起讨论。如果有更好的实现思路,希望能在回复中进行分享。谢谢!

 

三.  关键点的实现

  1. 原理

    (1)首先,我们需要一个配置文件,将程序中使用到的.dll文件名,窗体名等写入该配置文件。这个配置文件大家可以根据需要使用不同的类型,例如ini,xml,txt等等,在项目中,我们使用的是                  ini,大体结构如

###############################

#   文件名<例如:FuncConfig.ini>

###############################

[FUNCINFO]

#

功能编号=.dll文件名,窗体编号,功能名,功能所属模块编号,权限……

#

#

……

#

例如:

###############################

#   文件名<例如:FuncConfig.ini>

###############################

[FUNCINFO]

#

CMM0100L01=CMM0100,CMM0100F01,人员数据检索,报表管理,00

#

       

        当然,这个文件的格式可以根据程序的需要来更改。这里只是给出一个示例.注意:给出的窗体应该在指定的.dll文件中。

     (2)   程序使用给出的窗体编号去加载对应的.dll,然后在该.dll中将窗体类加载并show出来。

  2.代码实现

    (1)代码的流程如下图所示:

①     实现此功能的关键代码在FuncTransitCtrl类中,该类中,有两个方法,分别为ShowDialog和Show,分别对应两种show出窗体的方式。我们首先来看ShowDialog里面的代码。

  1. 首先,该方法接收两个参数,分别为窗体ID和需要传递给该窗体的参数集合,我们来看下方法定义
    ///         /// 以ShowDialog的方式显示指定的窗体        ///         /// 功能编号        /// 要发送的数据        /// 
被show出的窗体
public Form ShowDialog(String funcID, SendData sendData)

 

SendData是一个我们自己定义数据结构,专门用于存储要发送给窗体的参数。等会儿,我们将会看到它的代码。现在,我们只需要明白它是做什么的就可以了

  1. 接着,我们来讲解方法里面的每段代码。

     对窗体编号进行非空判断。

       Form form = null;//返回的窗体            if (String.IsNullOrEmpty(funcID))            {                gLogger.ErrorLog("FormTransitCtrl", "没有设定窗体ID。", null);                MessageBox.Show("指定的功能界面启动失败","错误",MessageBoxButtons.OK,MessageBoxIcon.Hand);                return form;            }

    gLogger是用于写日志的一个类型。相信大家在项目里都有自己一套专门处理日志的方法,在这里就不多说了。

     获取与该功能有关的信息。在这里,我们的程序就会去读取配置文件,把跟该窗体编号有关的信息全部读取出来,并保存到一个类中。我们分几步来看代码,首先,定义个用于保存功能相关                   信息的类型:

   ///     /// 保存功能信息的类型    ///     public class EihoFuncInfo    {        private string _DllFileName;        private string _EihoFuncID;        private string _FormID;        private string _WindowTitle;        public string DllFileName { get; set; }        public string EihoFuncID { get; set; }        public string FormID { get; set; }        public string WindowTitle { get; set; }    }

    接着,在FuncTransitCtrl定义一个静态方法,用于读取配置文件,并将获取的信息赋值给EihoFuncInfo的各字段。首先我们来看看如何读取配置文件中的信息(以我提供的配置文件格式为准。)这段代码大家可以简略看过,因为大家的配置文件格式不一定一致:

     ///         /// 读取ini文件中的信息        ///         /// ini文件名        /// 读取的段落        /// 读取的key        /// ini文件路径        /// 
读取到的信息
public static String GetIni(String strFileName, String strSection, String strKey, String strPath = "") { String str2 = strPath; //如果没有指明路径,则使用FuncTransitCtrl类中默认的ini路径 if (String.IsNullOrEmpty(strPath)) { str2 = FuncTransitCtrl.SystemPath + @"ini\"; } String tmpPath = str2 + strFileName; if (!File.Exists(tmpPath)) { //写日志 throw new FileNotFoundException(); } //读取key对应的value String lpReturnedString = GetPrivateIniProfileString(strSection, strKey, tmpPath); return lpReturnedString; }

    GetIni是主要的方法,它位于ConfigUtil类中。在该方法中,调用了一个名为GetPrivateIniProfileString的方法,这个方法会去到配置文件中去匹配段落,找到匹配的段落后,在查找该段落下的key,然后返回对应key的value值,该类型的完整代码如下:

View Code
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.IO;namespace Demo{    ///     /// 读取各种配置文件的类型    ///     public class ConfigUtil    {        //段落开始和结束的括号        private const String SECTION_START_STR = "[";        private const String SECTION_END_STR = "]";        //注释符号        private const String COMMENT_STR = ";";        private const String COMMENT_STR2 = "#";        //区分key与value的等号        private const String KEY_AND_VALUE_SEPARATOR = "=";        ///         /// 读取ini文件中的信息        ///         /// ini文件名        /// 读取的段落        /// 读取的key        /// ini文件路径        /// 
读取到的信息
public static String GetIni(String strFileName, String strSection, String strKey, String strPath = "") { String str2 = strPath; //如果没有指明路径,则使用FuncTransitCtrl类中默认的ini路径 if (String.IsNullOrEmpty(strPath)) { str2 = FuncTransitCtrl.SystemPath + @"ini\"; } String tmpPath = str2 + strFileName; if (!File.Exists(tmpPath)) { //写日志 throw new FileNotFoundException(); } //读取key对应的value String lpReturnedString = GetPrivateIniProfileString(strSection, strKey, tmpPath); return lpReturnedString; } /// /// 读取ini配置文件中指定key的值 /// /// 要读取的段落 /// 读取的key /// ini文件路径 ///
key对应的value
private static String GetPrivateIniProfileString(String strSection, String strKey, String strPath) { FileStream fs = new FileStream(strPath, FileMode.Open, FileAccess.Read); StreamReader sr = new StreamReader(fs, Encoding.UTF8); //是否查找key bool isSearchKey = false; //值 String strValue = String.Empty; try { String strLine = String.Empty; while ((strLine = sr.ReadLine()) != null) { String strGetSection = GetSection(strLine); //如果是一个段落 if (!String.IsNullOrEmpty(strGetSection)) { if (isSearchKey == true) { return strValue; } //如果找到的段落和参数不一致,则继续循环查找 if (strGetSection != strSection) { continue; } //找到了一致的,修改该变量,准备查找该段落下的key与value isSearchKey = true; } //不是一个段落,则查找key else { if (!isSearchKey) { continue; } //该行是否为一个注释 if (IsComment(strLine)) { continue; } //获取区分key,value的符号位置 int iPos = GetKeyAndValuePos(strLine); if (iPos < 0) { continue; } //获取key String strGetKey = strLine.Substring(0, iPos).Trim(); //如果与参数的不一致,继续循环查找 if (strGetKey != strKey) { continue; } //取得value strValue = strLine.Substring(iPos + 1).Trim(); break; } } } catch (IOException ex) { //写日志 throw new Exception(ex.Message, ex); } finally { sr.Close(); fs.Close(); } return strValue; } /// /// 获取字符串中,[]之间的内容 /// /// ///
[]之间的内容
private static String GetSection(String strLine) { String strTrimLine = strLine.Trim(); //如果没有段落开始的括号,则不是一个段落,不进行处理 if (!strTrimLine.StartsWith(SECTION_START_STR)) { return String.Empty; } int iPos = strTrimLine.IndexOf(SECTION_END_STR);//获得段落结束括号的位置 return strTrimLine.Substring(1, iPos - 1);//返回[]之间的字符串 } /// /// 判断该行是否为一个注释 /// /// ///
private static bool IsComment(String strLine) { String strTrimLine = strLine.Trim(); return (strTrimLine.StartsWith(COMMENT_STR) || strTrimLine.StartsWith(COMMENT_STR2)); } /// /// 获取区分key与value的符号的位置 /// /// 一个包含key=value的字符串 ///
区分符号的位置
private static int GetKeyAndValuePos(String strLine) { return strLine.IndexOf(KEY_AND_VALUE_SEPARATOR); } }}

    得到了配置文件中的信息后,我们就可以提取里面的信息了。代码如下:

     ///         /// 获取指定编号的功能的所有信息        ///         /// 功能编号        /// 
功能信息类型
public static EihoFuncInfo GetEihoFuncInfo(String funcID) { EihoFuncInfo funcInfo = null; if (!String.IsNullOrEmpty(funcID)) { try { //获取配置的value信息 String strFuncInfo = ConfigUtil.GetIni("FuncConfig.ini", "FUNCINFO", funcID,""); funcInfo = new EihoFuncInfo(); //分别提取各项信息 funcInfo.EihoFuncID = funcID; funcInfo.DllFileName = strFuncInfo.Split(',')[0]; funcInfo.FormID = strFuncInfo.Split(',')[1]; funcInfo.WindowTitle = strFuncInfo.Split(',')[2]; } catch (Exception ex) { //写日志的代码 throw ex; } } return funcInfo; }

  现在我们获得了必须的信息(窗体编号,所在程序集名称等),那么,接下来,我们就可以使用反射进行处理了。

       //获取功能相关的信息            EihoFuncInfo funcInfo = GetEihoFuncInfo(funcID);            if (funcInfo == null)            {                gLogger.ErrorLog("FormTransitCtrl", "没有取得与该功能相关的信息。", null);                MessageBox.Show("指定的功能界面启动失败", "错误", MessageBoxButtons.OK, MessageBoxIcon.Hand);                return form;            }            Assembly assembly = null;            try            {                assembly = Assembly.LoadFrom(funcInfo.DllFileName + ".dll");            }            catch (Exception ex)            {                gLogger.ErrorLog("FormTransitCtrl", "DLL加载失败,DLL名:" + funcInfo.DllFileName, null);                MessageBox.Show("指定的功能界面启动失败", "错误", MessageBoxButtons.OK, MessageBoxIcon.Hand);                return form;            }

    然后遍历该程序集里面的类型,找到与我们传入的FormID相匹配的,就将该窗体创建出来,代码如下:

       bool flag = false;            //获得窗体ID            String formID = "fm" + funcInfo.FormID;            //遍历程序集中的所有类型            foreach (Type type in assembly.GetTypes())            {                //找到与窗体匹配的                if (type.Name.ToUpper() == formID.ToUpper())                {                    flag = true;                    try                    {                        //调用构造函数创建                        form = (Form)type.GetConstructor(Type.EmptyTypes).Invoke(null);                        form.Text = funcInfo.WindowTitle;                        SetFuncID(form, funcID);                    }                    catch(Exception ex)                    {                        gLogger.ErrorLog("FormTransitCtrl", "窗体创建失败。DLL名:" + funcInfo.DllFileName, null);                        MessageBox.Show("指定的功能界面启动失败", "错误", MessageBoxButtons.OK, MessageBoxIcon.Hand);                        return form;                    }                    //处理需要传递的参数                    if ((sendData != null) && (sendData.GetSendData() != null))                    {                        IDictionaryEnumerator enumerator = ((Dictionary
)sendData.GetSendData()).GetEnumerator(); while (enumerator.MoveNext()) { KeyValuePair
current = (KeyValuePair
)enumerator.Current; String key = current.Key.ToString(); String value = current.Value.ToString(); ReceiveData.AddParame(key, value, form); } } _form = form; _form.ShowDialog(); form = _form; } } //没有找到匹配的窗体类型 if (!flag) { gLogger.ErrorLog("FormTransitCtrl", "没有找到匹配的窗体类型。DLL名:" + funcInfo.DllFileName, null); MessageBox.Show("指定的功能界面启动失败", "错误", MessageBoxButtons.OK, MessageBoxIcon.Hand); } return form;

    至此,我们的主体代码应该是完成了,FuncTransitCtrl的完整代码如下:

View Code
using System;using System.Collections;using System.Collections.Generic;using System.Linq;using System.Text;using System.Windows.Forms;using System.Reflection;namespace Demo{    ///     /// 对窗体进行控制的类型    ///     public class FuncTransitCtrl    {        ///         /// 配置文件路径        ///         public static String _systemPath;        private static Dictionary
funcIDTable = new Dictionary
(); private Logger gLogger = new Logger(); private Form _form; ///
/// 获取或者设置配置文件路径 /// public static String SystemPath { get { if (String.IsNullOrEmpty(_systemPath)) { return (Environment.GetEnvironmentVariable("USERPROFILE") + @"\AppData\Local\EMM\"); } return _systemPath; } set { _systemPath = value; } } ///
/// 以ShowDialog的方式显示指定的窗体 /// ///
功能编号 ///
要发送的数据 ///
被show出的窗体
public Form ShowDialog(String funcID, SendData sendData) { Form form = null;//返回的窗体 if (String.IsNullOrEmpty(funcID)) { gLogger.ErrorLog("FormTransitCtrl", "没有设定窗体ID。", null); MessageBox.Show("指定的功能界面启动失败","错误",MessageBoxButtons.OK,MessageBoxIcon.Hand); return form; } //获取功能相关的信息 EihoFuncInfo funcInfo = GetEihoFuncInfo(funcID); if (funcInfo == null) { gLogger.ErrorLog("FormTransitCtrl", "没有取得与该功能相关的信息。", null); MessageBox.Show("指定的功能界面启动失败", "错误", MessageBoxButtons.OK, MessageBoxIcon.Hand); return form; } Assembly assembly = null; try { assembly = Assembly.LoadFrom(funcInfo.DllFileName + ".dll"); } catch (Exception ex) { gLogger.ErrorLog("FormTransitCtrl", "DLL加载失败,DLL名:" + funcInfo.DllFileName, null); MessageBox.Show("指定的功能界面启动失败", "错误", MessageBoxButtons.OK, MessageBoxIcon.Hand); return form; } bool flag = false; //获得窗体ID String formID = "fm" + funcInfo.FormID; //遍历程序集中的所有类型 foreach (Type type in assembly.GetTypes()) { //找到与窗体匹配的 if (type.Name.ToUpper() == formID.ToUpper()) { flag = true; try { //调用构造函数创建 form = (Form)type.GetConstructor(Type.EmptyTypes).Invoke(null); form.Text = funcInfo.WindowTitle; SetFuncID(form, funcID); } catch(Exception ex) { gLogger.ErrorLog("FormTransitCtrl", "窗体创建失败。DLL名:" + funcInfo.DllFileName, null); MessageBox.Show("指定的功能界面启动失败", "错误", MessageBoxButtons.OK, MessageBoxIcon.Hand); return form; } //处理需要传递的参数 if ((sendData != null) && (sendData.GetSendData() != null)) { IDictionaryEnumerator enumerator = ((Dictionary
)sendData.GetSendData()).GetEnumerator(); while (enumerator.MoveNext()) { KeyValuePair
current = (KeyValuePair
)enumerator.Current; String key = current.Key.ToString(); String value = current.Value.ToString(); ReceiveData.AddParame(key, value, form); } } _form = form; _form.ShowDialog(); form = _form; } } //没有找到匹配的窗体类型 if (!flag) { gLogger.ErrorLog("FormTransitCtrl", "没有找到匹配的窗体类型。DLL名:" + funcInfo.DllFileName, null); MessageBox.Show("指定的功能界面启动失败", "错误", MessageBoxButtons.OK, MessageBoxIcon.Hand); } return form; } ///
/// 获取指定编号的功能的所有信息 /// ///
功能编号 ///
功能信息类型
public static EihoFuncInfo GetEihoFuncInfo(String funcID) { EihoFuncInfo funcInfo = null; if (!String.IsNullOrEmpty(funcID)) { try { //获取配置的value信息 String strFuncInfo = ConfigUtil.GetIni("FuncConfig.ini", "FUNCINFO", funcID,""); funcInfo = new EihoFuncInfo(); //分别提取各项信息 funcInfo.EihoFuncID = funcID; funcInfo.DllFileName = strFuncInfo.Split(',')[0]; funcInfo.FormID = strFuncInfo.Split(',')[1]; funcInfo.WindowTitle = strFuncInfo.Split(',')[2]; } catch (Exception ex) { //写日志的代码 throw ex; } } return funcInfo; } ///
/// 保存窗体对应的功能ID /// ///
///
public static void SetFuncID(Form form, String strFuncId) { if (funcIDTable.ContainsKey(form)) { funcIDTable.Remove(form); } funcIDTable.Add(form, strFuncId); } }}

    这是我们加载并show出窗体的代码,那么,传递的参数是怎么处理的呢?这里牵涉到两个类,分别为SendData和ReceiveData,看名字大家就知道是做什么的啦!首先来看看SendData的代码,如下:

using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace Demo{    public class SendData    {        private Dictionary
_datas; public SendData() { _datas = new Dictionary
(); } public Object GetSendData() { return _datas; } public void AddData(String key, String value) { if (_datas.ContainsKey(key)) { _datas.Remove(key); } _datas.Add(key, value); } public void RemoveData(String key) { _datas.Remove(key); } }}

    接下来,我们来看看ReceiveData类的代码。它与SendData有些不一样,虽然都是Key/Value集合,但是有那么多窗体,程序怎么知道哪些参数属于哪些窗体呢。所以,它内部使用一个嵌套的Key/Value集合来保存参数。key是某窗体的句柄值,而value则是属于该窗体的又一个Key/Value集合,我们来看代码,如下:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Windows.Forms;namespace Demo{    ///     /// 用于保存窗体之间接收参数的类型    ///     public class ReceiveData    {        //用于保存接收参数的集合        private static Dictionary
> _parameTable; static ReceiveData() { _parameTable = new Dictionary
>(); } ///
/// 添加参数到集合 /// ///
参数key ///
参数value ///
参数所属窗体 public static void AddParame(String key, String value, Form oForm) { IntPtr handle = oForm.Handle; Dictionary
parames = null; if (_parameTable.ContainsKey((long)handle)) { parames = _parameTable[(long)handle]; } else { parames = new Dictionary
(); _parameTable.Add((long)handle, parames); } if (parames.ContainsKey(key)) { parames.Remove(key); } parames.Add(key, value); } ///
/// 获取指定key的value /// ///
参数key ///
参数所属窗体 ///
参数value
public static String GetParame(String key, Form oForm) { IntPtr handle = oForm.Handle; if (_parameTable.ContainsKey((long)handle)) { Dictionary
parames = _parameTable[(long)handle]; if (parames.ContainsKey(key)) { return parames[key]; } } return ""; } }}

    代码非常简单,这里就不作过多的解释了。然后我们在再次回到FuncTransitCtrl类的ShowDialog方法中,在反射创建出窗体和show出窗体之间,加入传参数的代码,如下:

            //处理需要传递的参数                    if ((sendData != null) && (sendData.GetSendData() != null))                    {                        IDictionaryEnumerator enumerator = ((Dictionary
)sendData.GetSendData()).GetEnumerator(); while (enumerator.MoveNext()) { KeyValuePair
current = (KeyValuePair
)enumerator.Current; String key = current.Key.ToString(); String value = current.Value.ToString(); ReceiveData.AddParame(key, value, form); } }

至此,主体代码就完成了。接下来是程序的配置。

(1)  首先,整个项目应该有个入口的.exe,例如入口为A.exe,另外有B.dll,C.dll等等,在这些.dll里面有很多窗体。在任何一个.dll或者.exe里面,我们都可以使用ShowDiglog来反射调用指定的窗体。

(2)  其次,配置文件的路径。默认情况下,我是使用Environment.GetEnvironmentVariable(“USERPROFILE”)来获取C:\Users\[当前用户]这个目录,将我们的配置文件放在这个目录下。回忆一下FuncTransitCtrl,里面有个SystemPath的属性,用于获取和设置配置文件路径,代码如下:

     ///         /// 获取或者设置配置文件路径        ///         public static String SystemPath        {            get            {                if (String.IsNullOrEmpty(_systemPath))                {                    return (Environment.GetEnvironmentVariable("USERPROFILE") + @"\AppData\Local\EMM\");                }                return _systemPath;            }            set            {                _systemPath = value;            }        }

最后,完整的代码下载在。在使用之前仍然再在这里啰嗦几句。

(1)              FuncConfig.ini配置文件放到C:\Users\ja\AppData\Local\EMM\ini目录下,当让,你也可以修改程序的SystemPath,指向你自己的目录

(2)              Demo,CMM0100,MainDemo编译后生成的程序集统一放在了bin目录下,而不是他们各自的bin目录。

(3)              注意窗体和程序集的命名应和配置文件中的保持一致

(4)              最后,示例中一些无关主体功能实现的代码(例如日志之类的),给出的是空实现。

结语

              不知道大家的项目中是否也是这样处理,我想,使用这种方式加载窗体,好处是显而易见的。不同的模块由不同的人开发,编译打包为dll,不同的.dll之间的窗体调用通过反射封装。整个项目走一个.exe入口启动。

              好了,这一集中,分享的是ShowDialog的方式,下一集中,跟大家分享Show的方式。谢谢!

 

转载于:https://www.cnblogs.com/donet_code/archive/2013/01/17/2835180.html

你可能感兴趣的文章
redhat tomcat
查看>>
统计数据库大小
查看>>
IO流的学习--文件夹下文件的复制
查看>>
第十六章:脚本化HTTP
查看>>
EXCEL表中如何让数值变成万元或亿元
查看>>
nginx在响应request header时候带下划线的需要开启的选项
查看>>
Linux下DHCP服务器配置
查看>>
AndroidStudio中导入SlidingMenu报错解决方案
查看>>
myeclipse显示行号
查看>>
编写高性能的java程序
查看>>
Spring 的配置详解
查看>>
linux已经不存在惊群现象
查看>>
上位机和底层逻辑的解耦
查看>>
关于微信二次分享 配置标题 描述 图片??
查看>>
springcloud使用zookeeper作为config的配置中心
查看>>
校园火灾Focue-2---》洗手间的一套-》电梯
查看>>
css控制文字换行
查看>>
bzoj1913
查看>>
L104
查看>>
分镜头脚本
查看>>