一引言对于ncf(net精简版的英文缩写)开发人员应用程序选项保存一般只有两种途径选择
将选项的值写入注册表但如果所有应用程序都将值大量写入注册表的做法最终将导致注册表过大占用系统资源而影响系统的运行效率而且这就是很多软件在硬启设备之后不得不重新安装的原因根据现代程序编写中程序尽可能与系统独立的思想这种做法不推荐使用
将选项值以一个初始化文件的方式保存这样做可以最大限度避免系统资源占用提高程序运行独立性这种做法个人认为是较为可取的方案并且这种做法完整版中实现非常简单可以直接用Xml序列化类来实现但在专为智能移动设备定做的net精简版中由于不提供XML序列化属性使得保存和使用程序选项变得郁闷起来程序开发人员不得不对每一个程序选项作写入/读取文件的编码这个枯燥无味的步骤绝对不会是一件有趣的事情
二功能概述
本文中我将利的反射功能构建一个自动完成初始化文件的保存/读写功能的类在这个类中只要程序作者在类内部按程序选项的名称定义好类的内部成员变量(由于这个步骤仍然需要程序员进行类内的手工编码所以称这个类为半自动初始化类)这个类就自动将程序选项从初始化文件中保存/读取的工作程序员不必再进行繁琐的读写文件部分的编码而且这个类的构建还可以有一个好处由于应用程序的选项都以成员变量的形式保存在类的内部程序员可以利用VS提供的自动列出变量成员的功能查询初始化文件的选项例如这样写 string myAPPname= tobjAPPOptionGeneralAPPName据我所知记住大量的程序选项的确切字符也不是什么好玩的事哦)
三程序实现先决条件分析
初始化文件内容的需求
我们首先分析观察一个标准的windows初始化文件winini内容
[windows]
load=
run=
NullPort=None
device=HP LaserJet L PCLPCLEMS\\EAB\HPLaserJ
[Desktop]
Wallpaper=(无)
TileWallpaper=
WallpaperStyle=
该初始化文件的内容用方括号括住的部分我们称为初始化文件的节每一节下都组织了一系列与节有相应功能的程序选项如desktop节下就含有桌面墙纸(Wallpaper)/桌面墙纸铺设(WallpaperStyle)的设置在初始化文件中程序的选项大都可以用字符串/数字这些简单的数据类型进行保存
根据这一需求考虑到目前中使用xml文件非常方便而且使用xml格式除可实现常规windows初始化文件的功能外还可以多出树形结构组织的优势所以本文设计的初始化文件确定采用xml文件格式并作以下格式的XML文件的元素定义
Net对象以XML元素保存使用的格式定义
Net对象的定义
<Class ObjectName=对象名称 ObjectType(数据类型)=数据类型 > 数据内容 </数据类型>
数组的定义 目前本类中实现的数组只支持string的一维数组
<Array ObjectName=对象名称 ObjectType=数据类型 Length=数组大小> 元素定义</Array>
如果数组数组为nothing则格式如下
<Array ObjectName=对象名称 ObjectType=数据类型 Length=>nothing</Array>
简单对象的定义intString等
<SimpleObject ObjectName=对象名称 ObjectType(数据类型)=数据类型 > 数据内容 </数据类型>
当SimpleObject代表数组内的元素时objectname代表数组的维数
ObjectName ObjectType Lenght 属性的使用举例如下
例如Redim maiTest() As String
ObjectName取值为maiTestObjectType取值为string[]Length 值为
中有一种称之为反射的功能可以枚举特定类型对象所包含的成员变量的类型及储存值这个功能经常被一些普通程序员忽略认为用途并不大但在本文中这一功能将成为构建半自动化初始化对象的核心我们正需要这种功能将写在初始化类中的变量类型和值自动向初始化文件保存或读取需要使用的反向类型方法及说明如下表
四程序实现核心代码注释
我将这个半自动初始化文件类命名为clsAPPOption类内结构及包含过程的功能说明如下
两个区域#Region 应用程序使用的选项结构定义#Region 应用程序选项的变量声明中的内容是按初始化选项级组织的类及类的实例每一个类表示程序选项的一个初始化节必须由最终使用者根据实际选项需要自行手工补充
fnGetAppDirectory:取得应用程序的运行目录
sbInitialDefaultAPPOption设置程序选项的默认初始值(这个过程中的代码需根据实际需要手工修改)
fnSaveAppOption将类内的程序选项保存到一个指定的文件中 (这个过程中的部分代码需根据实际需要手工修改)
fnLoadAppOption在指定的文件中读取应用程序的选项信息并保存到当前类中(这个过程中的部分代码需根据实际需要手工修改)
fnXMLElementToSimpleObject将一个XMLElement转为它代表的简单对象所谓简单对象就是诸如int\int之类的基本net对象
fnXMLElementToClassObject将一个XMLElement转换为它代表的类对象
fnXMLElementToArray将一个XMLElement转为它代表的数组
fnArrayToXML将一个数组放入XML文件中目前只支持一维数组例如dim aString() as string
fnClassObjectToXML将一个类对象转换为xml元素的表示形式
fnSimpleObjectToXML将一个简单对象改为XML元素表示
注其中fnXMLElementToXXXX和fnXXXXToXML功能相对应互为反函数
程序的实现是非常简单的fnClassObjectToXML对指定的类进行反射操作使用类的类型的GetFields方法枚举类内的成员变量信息然后根据成员变量的类型调用fnSimpleObjectToXML或fnArrayToXML在函数结束的时候将要转换的类以一个XMLElement对象的形式返回
tobjClassObjectType = ni_objClassObjectGetType 取得类的类型以利于反射调用
…其它代码
REM 以结构内的所有值进行反射取值 并存入xml对象中
For Each tobjFieldInfo In tobjClassObjectTypeGetFields
If tobjFieldInfoFieldTypeIsArray = False Then 只是一个简单类型直接取得值
tobjXMLElement = fnSimpleObjectToXML(tobjFieldInfoGetValue(ni_objClassObject) _
ni_objXMLDocument _
tobjFieldInfoName)
tobjXMLClassObjectElementAppendChild(tobjXMLElement)
Else
REM 如果是一个数组类型则进行数组方法的调用以取得值
目前只支持一维数组元素
tobjXMLElement = fnArrayToXML(tobjFieldInfoGetValue(ni_objClassObject) ni_objXMLDocument tobjFieldInfoName tobjFieldInfoFieldTypeFullName)
将数组对象放入结构的xml对象中
tobjXMLClassObjectElementAppendChild(tobjXMLElement)
End If
fnSimpleObjectToXML的实现也很简单根据前文确立的简单对象的定义fnSimpleObjectToXML过程所要生成的xml对象的几个要素可以这样获取ObjectName在f nClassObjectToXML作反射后已经获取并以参数传递的方式在调用fnSimpleObjectToXML时提供了 ObjectType数据类型则可以利用ni_objSimpleObjectGetTypeFullName方法取得 ni_objSimpleObject就是在函数调用时传入的简单对象的值
在编写fnArrayToXML过程时出现了一点小问题由于调用方不可能要求数组的元素逐个传入所以对数组的元素个数以及元素的值无法在函数中以对象反射的方式直接获取幸运的是在Net反射操作中可以利用Invoke方法来调用原始对象内的函数或属性过程而数组的共享方法GetLengthGetValue功能适好是取数组元素个数及指定下标的元素值所以问题立刻迎刃而解
利用反射调用数组的getLenght方法取得数组的大小这里仅支持一维数组但对于初始化文件已足够用了
tobjXMLAttribute = ni_objXMLDocumentCreateAttribute(ArrayLength)
tobjMethodInfo = tobjArrayTypeGetMethod(GetLength)
ReDim taobjParameter()
taobjParameter() =
tiTempa = tobjMethodInfoInvoke(ni_objArray taobjParameter)
tobjXMLAttributeValue = tiTempa
tobjXMLElementSetAttributeNode(tobjXMLAttribute)
tobjXMLAttribute = Nothing
加入数组的内的元素
For tiLoopA = To tiTempa
利用反射取得数组的GetValue方法取得实际元素的值
ReDim taobjParameterType()
taobjParameterType() = GetType(Integer)
tobjMethodInfo = tobjArrayTypeGetMethod(GetValue taobjParameterType)
ReDim taobjParameter()
taobjParameter() = tiLoopA
tobjTempa = tobjMethodInfoInvoke(ni_objArray taobjParameter)
将数组元素放入对xml对象中
tobjXMLElementA = fnSimpleObjectToXML(tobjTempa ni_objXMLDocument tiLoopA)
tobjXMLElementAppendChild(tobjXMLElementA)
Next tiLoopA
从XML转为net对象的过程基本都没有什么技术难度我只是根据xml中包含的ObjectType信息简单获得简单对象的类型然后直接调用net强制转换类的功能将XML文件中指定的对象储存的数值重新恢复为原来的net对象代码如下
根据XMLelement结构元素中指定的对象类型建立对象
tobjType = TypeGetType(tobjXmlSimpleObjectElementGetAttribute(ObjectType))
tobjReturnSimpleObject = ConvertChangeType(tobjXmlSimpleObjectElementInnerText tobjType Nothing)
对于Xml转为数组用数组转为类对象的方法可以参阅本文所附源代码
初始化文件类实例的调用
类定义完成以后可以在程序的全局范围定义初始化类的实例在模块内定义就可以了
Public gobjAppOption As New clsAPPOption
可以在程序启动的时候读初始化文件信息到类中我是放在主窗体的load事件中的
读取程序的选项
gobjAppOptionfnLoadAppOption()
可以在程序的任意地方保存当前半自动初始化类的值到xml文件中我在主窗体的closed事件中加入保存代码
gobjAppOptionfnSaveAppOption() 保存应用程序的选项
程序的任意地方你都可以调用半自动初始化文件类的实例来读取/保存程序选项的当前值
可以很好地利用VS提供的自动列出成员的功能列出程序选项
读取选项的示例代码
MessageBoxShow(gobjAppOptionmobjAPPGeneralOptionastrShowFileFilter())
保存选项的示例代码
gobjAppOptionmobjAPPGeneralOptionastrHideFileFilter() = *zip
虽然这个半自动化初始化文件类还是需要手工添加少量代码(在源代码明确注释要手工添加的部分)但还是那句老话如果一切事情电脑都会做的时候离程序员下岗的日子就不远了