"一个优秀的Delphi程序员,不仅要会写控件,还要会使用控件。"
我还是一个半瓢水的程序员,因此目前为止我所能努力达到的境界是:
一个半瓢水的程序员,管他会不会写控件,只要能拿来改就可以了。
使用过Delphi的朋友都知道,我们在设计Delphi应用程序的某一功能时总是希望能够有现成的第三方免费控件可以拿来使用。但实际情况往往是,网上的控件确实种类繁多,优秀作品也数不胜数,但真正完全能够符合设计者具体要求的控件却很少。究其原因,不外乎以下几条
1、国人开发的应用程序大多具有中国特色,而优秀的第三方控件往往是国外程序开发人员开发的(大名鼎鼎的Rxlib,知道吧,俄罗斯人的作品)。功能定义上的差别导致了控件的不可用性,看看QuickReport就知道了(当然,QuickReport编的也很烂)。
2、真正优秀的第三方控件总是从大局上把握控件所能达到的功能,细节问题考虑的不多。而我们设计的程序通常要考虑到很多操作细节上的问题(一般是客户的要求,有时会有些变态)
3、对于许多很有特色的控件,我们所需要的只是其中一部分功能,但是它被整合到了某一个大的方法中,无法为我们所用。
4、优秀和免费总是两个矛盾,天下没有免费的午餐。
种种原因,限制了开发者对控件资源的充分利用。其实,很多控件的代码都提供了开放的接口。只要大家对控件的原理有一定的了解,然后对别人的控件代码作一捏捏的修改,就可以无缝的添加自己需要的功能,或剔除冗余的功能。既达到了开发的目的,又避免了重写代码的麻烦,一举两得(好像很不负责任啊,没办法了,半瓢水嘛)。关于控件的原理,应该有很多文章介绍过了,在这里我想通过一个具体的实例来介绍一下怎样将别人的控件"拿来"为我所用。
这个例子是关于如何将一个文本数据导入导出控件作一些简单的修改之后拿来使用的。
◆程序功能
将图书馆的ISO文件中的部分数据转到Oracle8数据库中。
◆设计要求
1.显示导入进度条。
2.在导入过程中,如果某条纪录导入失败,不显示异常,而将导入失败的纪录记入日志。
◆设计思路
程序的关键在文本数据字段的分离。通常的做法,都是先将字符串进行处理(RegulateString),然后把串中每个字符同分割符(可以是空格,逗号等)比较,将不是分割符的字符追加到一个串中(GetRecordItem),得到一个字段的内容。通过一个循环(循环次数由GetItemNum来定),就可以将一个字符串分成几个字段。最后的工作就是将分离出来的数据对号入座加入数据库。
按照上面的思路,利用Delphi提供的已有函数和过程,实现起来应该不难,但问题是,我可不想每次编文本导入程序的时候,都把什么这啊那的函数过程重新定义一遍,哎,最烦的就是重复性的工作了.那么有没有现成的控件将上述过程都封装起来呢?PS:又不用我编呢?
答案是肯定的!前几天刚刚下了一个免费控件TPgCSV,据说可以实现文本的导入和导出.翻出来一看,正是我想要的。
在深入到下面的内容之前,有必要对该控件的类声明部分作一定了解
  //中文部分为笔者所作的注释
 //注意:
 //在该控件中,Export代表将文本数据导入到数据库,Import代表从数据库导出到文本。??? 怎么和我理解的
 //导入导出概念刚好是反的 :)
 type
 //在处理数据产生异常时,可选择继续还是中止
 TPgCSVErrorResponse = (pgcsvAbort, pgcsvIgnore);
 //进程监控事件声明,可以将导入/导出的进度作为参数传出
 TPgCSVProgressEvent = procedure (Sender : TObject; AProgress: LongInt; var StopIt: Boolean) of object;
 //发生异常时的事件处理声明,异常信息通过该接口传给程序员。
 TPgCSVExportErrorEvent = procedure (Sender : TObject; Mess: string; RecNo: LongInt; var Response:TPgCSVErrorResponse) of object;
 TPgCSV = class(TComponent)
 private
 FDataset : TDataset;
 FCSVMap,
 FCSVFile,
 FDateFormat,
 FIgnoreStr : string;
 FSeprator,
 FDelimiter,
 FFieldIndicator : Char;
 FAutoOpen,
 FUseDelimiter,
 FSilentExport,
 FTrimData,
 FStop,
 FEmptyTable : Boolean;
 FBeforeOpenTable,
 FAfterOpenTable,
 FBeforeCloseTable,
 FAfterCloseTable,
 FBeforeEmptyTable,
 FAfterEmptyTable,
 FBeforeExport,
 FAfterExport,
 FBeforeImport,
 FAfterImport,
 FOnAddRecord : TNotifyEvent;
 FExportProgress,
 FImportProgress : TPgCSVProgressEvent;
 FExportError : TPgCSVExportErrorEvent;
 FMapItems,
 FDefaultInt : Integer;
 FBufferSize : LongInt;
 FFieldCache : TList;
 protected
 FFile : TextFile;
 //以下就是我所说的希望封装的部分
 function CountMapItems:Integer;//计算映射字符串的字段个数
 function GetMapItem(ItemIndex:Integer;var AField:Boolean):string;//提取映射字符串的字段
 function GetCSVRecordItem(ItemIndex:Integer;CSVRecord:string):string;//提取CSV文件字符串中的某一字段
 function BuildMap:string;//自动创建映射,如果CSVMap一栏为空的话,会由它来产生映射字符串
 function ExtractWord(Item: Integer;S, WordDelim: string): string;//提取文本数据字符串/映射字符串中的某一字段
 function WordCount(const S ,WordDelim: string): Integer;//计算文本数据字符串/映射字符串中的字段数目
 function WordPosition(Item: Integer; const S, SubStr: string): Integer;//计算子字符串在字符串中的位置
 public
 constructor Create(AOwner: TComponent); override;
 published
 //properties
 property Dataset : TDataset read FDataset write FDataset;
 //设置要导入或导出的目标数据集.
 property CSVMap : string read FCSVMap write FCSVMap;
 //CSV 文本数据文件到数据库字段值的映射字符串.控件通过该映射决定文本中的哪些数据要导入及要导入哪个字段.
 property CSVFile : string read FCSVFile write FCSVFile;
 //CSV 文件格式,其实就是文本数据文件。CSV代表什么意思?呵呵,我也不知道
 property Seprator : Char read FSeprator write FSeprator;
 //分隔符,可以是空格,也可以是,、;、#等符号
 property FieldIndicator : Char read FFieldIndicator write FFieldIndicator;
 //字段标识符.
 property AutoOpen : Boolean read FAutoOpen write FAutoOpen;
 //将AutoOpen设为True可以在处理数据前自动打开要导入的数据表并在操作完毕后自动关掉它。
 property IgnoreString : string read FIgnoreStr write FIgnoreStr;
 //忽略纪录的标识串.
 //举例来说
 //IgnoreString:='(ignore)';
 //CSVMap:='$Name,(ignore),$Age';
 //在这种情况下,CSVToDataSet方法,即导入数据方法将忽略文本文件中的第二列的字段。
 property Delimiter : Char read FDelimiter write FDelimiter;
 //在某些CSV文档中标识字符串纪录的标识符,比如,"john","boy",12中的",在这种情况下,TPgCSV
 //会忽略这些标识符。
 property EmptyTable : Boolean read FEmptyTable write FEmptyTable;
 //只在从数据库导出(DataSetToCSV)方法中有效,作用是创建一个新的CSV文件。
 property UseDelimiter : Boolean read FUseDelimiter write FUseDelimiter;
 //是否有Delimiter。
 property SilentExport : Boolean read FSilentExport write FSilentExport;
 //若该属性为True,应用程序将不显示数据操作时的异常,而将异常信息通过一个接口传给程序员处理.
 property DateFormat : string read FDateFormat write FDateFormat;
 //设定CSV文件中日期数据的格式。
 property TrimData : Boolean read FTrimData write FTrimData;
 //是否去掉数据头尾的空格.
 property DefaultInt : Integer read FDefaultInt write FDefaultInt;
 //整/实形数据转换出错后的默认值
 property BufferSize : LongInt read FBufferSize write FBufferSize;
 //CSV 文件的缓冲值,以字节为单位,可以加快导入和导出数据的速度。
 //events
 property BeforeOpenTable : TNotifyEvent read FBeforeOpenTable write FBeforeOpenTable;
 property AfterOpenTable : TNotifyEvent read FAfterOpenTable write FAfterOpenTable;
 property BeforeCloseTable : TNotifyEvent read FBeforeCloseTable write FBeforeCloseTable;
 property AfterCloseTable : TNotifyEvent read FAfterCloseTable write FAfterCloseTable;
 property BeforeEmptyTable : TNotifyEvent read FBeforeEmptyTable write FBeforeEmptyTable;
 property AfterEmptyTable : TNotifyEvent read FAfterEmptyTable write FAfterEmptyTable;
 property BeforeImport : TNotifyEvent read FBeforeImport write FBeforeImport;
 property AfterImport : TNotifyEvent read FAfterImport write FAfterImport;
 property BeforeExport : TNotifyEvent read FBeforeExport write FBeforeExport;
 property AfterExport : TNotifyEvent read FAfterExport write FAfterExport;
 property ExportProgress : TPgCSVProgressEvent read FExportProgress write FExportProgress;
 //进程监控事件。每完成一条文本数据的导入就触发该事件。
 property ImportProgress : TPgCSVProgressEvent read FImportProgress write FImportProgress;
 property OnAddRecord : TNotifyEvent read FOnAddRecord write FOnAddRecord;
 property ExportError : TPgCSVExportErrorEvent read FExportError write FExportError;
 //发生异常时交由该事件处理,异常信息通过该接口传给程序员。
 //methodes
 //整个控件的核心内容
 procedure CSVToDataset;//将文本导入到数据集的方法
 procedure DatasetToCSV;//将数据集的数据导入到文本的方法
 end;
 procedure Register;
 {略}
 implementation
 {略}
 end. 
从声明部分中我们可以看到,TPgCSV将文本数据的导入导出全部封装到了CSVToDataSet(文本数据导入)和DataSetToCSV(文本数据导出)两个方法中.开发者可以在设计阶段直接将文本文件同要导入/出的数据库相连,然后在程序运行当中调用这两个方法就可以了,根本不用理会那些函数什么的,相当的方便.