Delphi的“动态窗体”技术实际应用

来源:互联网 发布:模板制作软件 编辑:程序博客网 时间:2024/05/15 23:45
  1. 在Delphi可视化设计环境中,允许程序员在代码编辑器中以文本的方式浏览和修改DFM文件内容。当用File/Open命令直接打开DFM文件或者选择窗体设计窗口的弹出式菜单上的View as Text命令时,就会在编辑器中出现文本形式的信息。在一些资料中将这种文本形式称之为窗体设计脚本。Delphi提供的这种脚本编辑功能是对Delphi可视化设计的一大补充。当然这个脚本编辑能力是有限制的,比方说不能在脚本任意地添加和删除部件,因为代码和DFM脚本是紧密相连的,任意添加和修改会导致不一致性。但在动态生成的DFM文件中,就不存在这一限制。
  2.   实际上,DFM文件内容是二进制数据,它的脚本是经过Delphi开发环境自动转化的,而且Delphi VCL中的Classes库单元提供了在二进制流中的文件DFM和它的脚本之相互转化的过程。它们是ObjectBinaryToText和ObjectTextToBinary、ObjectResourceToText和ObjectTextToResource。
  3.   ObjectBinaryToText过程将二进制流中存储的部件转化为基于文本的表现形式,这样就可以用文本处理函数进行处理,还可以用文本编辑器进行查找和替代操作,最后可以将文本再转化成二进制流中的部件。
  4.   ObjectTextToBinary过程执行的功能与ObjectBinaryToText相反,将TXT文件转换为二进制流中的部件,而且只要TXT文件内容的书写符合DFM脚本语法,ObjectTextToBinary可将任何程序生成的TXT文件转换为部件,这一功能也为DFM文件的动态生成和编辑奠定了基础。
  5. 如何在运行过程中将本窗体保存成一个文本格式的.dfm文件?
  6. zswang(伴水) (2001-11-21 9:52:59) 得0
  7. function ComponentToString(Component: TComponent): string;
  8. var
  9. BinStream: TMemoryStream;
  10. StrStream: TStringStream;
  11. s: string;
  12. begin
  13. BinStream := TMemoryStream.Create;
  14. try
  15. StrStream := TStringStream.Create(s);
  16. try
  17. BinStream.WriteComponent(Component);
  18. BinStream.Seek(0, soFromBeginning);
  19. ObjectBinaryToText(BinStream, StrStream);
  20. StrStream.Seek(0, soFromBeginning);
  21. Result := StrStream.DataString;
  22. finally
  23. StrStream.Free;
  24. end;
  25. finally
  26. BinStream.Free
  27. end;
  28. end{ ComponentToString }
  29. function StringToComponent(Value: string; Instance: TComponent): TComponent;
  30. var
  31. StrStream: TStringStream;
  32. BinStream: TMemoryStream;
  33. begin
  34. StrStream := TStringStream.Create(Value);
  35. try
  36. BinStream := TMemoryStream.Create;
  37. try
  38. ObjectTextToBinary(StrStream, BinStream);
  39. BinStream.Seek(0, soFromBeginning);
  40. Result := BinStream.ReadComponent(Instance);
  41. finally
  42. BinStream.Free;
  43. end;
  44. finally
  45. StrStream.Free;
  46. end;
  47. end{ StringToComponent }
  48.  
  49. 回复人: zswang(伴水) (2001-11-21 9:54:28) 得0
  50. procedure TForm1.Button1Click(Sender: TObject);
  51. begin
  52. Memo1.Text := ComponentToString(Self);
  53. end;
  54.  
  55. 回复人: zswang(伴水) (2001-11-21 9:58:13) 得0
  56. procedure TForm1.Button2Click(Sender: TObject);
  57. begin
  58. StringToComponent(
  59. 'object Label1: TLabel'#13#10 +
  60. ' Left = 232'#13#10 +
  61. ' Top = 56'#13#10 +
  62. ' Width = 26'#13#10 +
  63. ' Height = 13'#13#10 +
  64. ' Caption = #20320#22909'#13#10 +
  65. ' Font.Charset = GB2312_CHARSET'#13#10 +
  66. ' Font.Color = clRed'#13#10 +
  67. ' Font.Height = -13'#13#10 +
  68. ' Font.Name = #23435#20307'#13#10 +
  69. ' Font.Style = []'#13#10 +
  70. ' ParentFont = False'#13#10 +
  71. 'end'#13#10, Label1);
  72. end;
  73. //要注册类
  74. ==end=================================
  75. 好了,理解了上面的这段文字,一些朋友就会自然想到,利用这几个函数应该可以弄出点有用的东西出来,我就弄出了一点应用,并全面应用到了项目中,现在我来给大家完整描述出来:
  76. 首先我要求我的程序有如下能力:
  77. 1. 我的程序的窗体是可以动态替换的,不用编译Exe,只要替换一个DFM窗体设计脚本就可以了(当然,你可以重新包装一下这个DFM文件,比如换成txt后缀名等)。
  78. 2. 我可以预览所有的DFM文件,让它变成实际的Form察看。
  79. 不要小看这两点,在很多情况下,这意义非常重大,举几个例子①开发阶段,可以把界面设计和程序设计完全分开,分工进行②现场维护时,有些界面的调整和功能设置不需要再找源代码到Delphi下去编译一遍了,老出差做Mis类的朋友应该能从这点体会出好处③某些功能界面的升级简单了不少,只要让用户下载一个DFM文件覆盖原来的就可以了。
  80. 好,不费话了,下面详细说明怎么达到以上两点要求。
  81. 显然我们要让一段文本变成一个Form,那么就用这个函数:
  82. function StringToComponent(Value: string; Instance:TComponent): TComponent;
  83. var
  84. StrStream:TStringStream;
  85. BinStream: TMemoryStream;
  86. begin
  87. StrStream := TStringStream.Create(Value);
  88. try
  89. BinStream := TMemoryStream.Create;
  90. try
  91. ObjectTextToBinary(StrStream, BinStream);
  92. BinStream.Seek(0, soFromBeginning);
  93. Result := BinStream.ReadComponent(Instance);
  94. finally
  95. BinStream.Free;
  96. end;
  97. finally
  98. StrStream.Free;
  99. end;
  100. end;
  101. 但是,所有的Class必须是注册过的,例如,如下的Form1FRM.DFM文件
  102. object Form1: TForm1
  103. Left = 222
  104. Top = 168
  105. Width = 485
  106. Height = 290
  107. Caption = 'Form1'
  108. Color = clBtnFace
  109. Font.Charset = DEFAULT_CHARSET
  110. Font.Color = clWindowText
  111. Font.Height = -11
  112. Font.Name = 'MS Sans Serif'
  113. Font.Style = []
  114. OldCreateOrder = False
  115. PixelsPerInch = 96
  116. TextHeight = 13
  117. object Panel1: TPanel
  118. Left = 0
  119. Top = 0
  120. Width = 477
  121. Height = 33
  122. Align = alTop
  123. TabOrder = 0
  124. object BitBtn1: TBitBtn
  125. Left = 4
  126. Top = 4
  127. Width = 75
  128. Height = 25
  129. Caption = 'OK'
  130. TabOrder = 0
  131. end
  132. end
  133. object Memo1: TMemo
  134. Left = 0
  135. Top = 33
  136. Width = 477
  137. Height = 230
  138. Align = alClient
  139. TabOrder = 1
  140. end
  141. end
  142. 你应该这么使用,
  143. var list:TstringList;form:TForm
  144. list.Lines.LoadFromFile(‘Form1FRM.DFM’);
  145. RegisterClass(TForm1);
  146. RegisterClass(TPanel);
  147. RegisterClass(TBitBtn);
  148. RegisterClass(TMemo);
  149. form := StringToComponent(list.Lines.Text,nil);
  150. form.ShowModal();
  151. 这样就能显示出一个窗体了。
  152. 但是这有个问题,Delphi自带的VCL控件是固定的,用RegisterClass(…)注册一遍没有问题,可TForm1不是,如果连TForm1都要注册的话,就无法达成第2点要求。我们可以变通一下,因为所有的Form都是从Tform继承的,所以,应该都可以用注册Tform来取代,因此,有了下面这样一个函数:
  153. function LoadTextForm(FileName:String):TForm;
  154. var
  155. list:TStrings;
  156. FirstLine:String;
  157. iPos : Integer;
  158. Form : TForm;
  159. begin
  160. Result := nil;
  161. if FileExists(FileName)=False then
  162. Exit;
  163. Form := TForm.Create(Application);
  164. list := TStringList.Create;
  165. try
  166. list.LoadFromFile(FileName);
  167. if list.Count=0 then
  168. Exit;
  169. FirstLine := list[0];
  170. iPos := Pos(': ',FirstLine);
  171. if iPos = 0 then //找不到': ',格式不对
  172. Exit;
  173. list[0]:=Copy(FirstLine,1,iPos)+' TForm';
  174. DeleteErrorLines(list);
  175. StringToComponent(list.Text,Form);
  176. Result := Form;
  177. except
  178. Form.Free;
  179. Result := nil;
  180. end;
  181. list.Free;
  182. end;
  183. 原理就是读入DFM文件后把窗体的类别偷换成Tform。其中还有一个函数:
  184. procedure DeleteErrorLines(list:TStrings);
  185. var
  186. i:Integer;
  187. line:String;
  188. begin
  189. if list.Count=0 then
  190. Exit;
  191. i:=0;
  192. while i<list.Count do
  193. begin
  194. line := Trim(list[i]);
  195. if Copy(line,1,2)='On' then
  196. list.Delete(i)
  197. else
  198. Inc(i);
  199. end;
  200. end;
  201. 这个函数是把凡是含有“On”开头的行删除,应为在Delphi中,所有控件的事件都是以“On”开头,删除了这样的行,就能保证StringToComponent(list.Text,Form);不出错,用以上的两个函数就可以写一个DFM窗体察看器了,到目前为止,我还没有搜到哪个人发布了DFM窗体察看器。这样我们就完成了第2个要求。
  202. 实际应用中,一个窗体几乎肯定会有事件处理函数,所以我们要达成第1个要求。我这儿提供了两个方案,各有优缺点:
  203. 方案一:
  204. 程序员在开发时,在窗体的FormCreate(…)中,用LoadTextForm(…)生成窗体文件,然后把窗体上的控件全部移到本窗体上,最后查找窗体上的控件,动态设置事件处理函数。这个方法要求有一套好的控件命名规则,而且开发比较烦琐,享受不到Delphi的IDE所见即所得,自动生成事件关联代码的好处了。不过对Form文件的制作人员限制很小,他们可以直接用Delphi来制作窗体。
  205. 方案二:
  206. 用这个函数
  207. procedure ReadForm(aFrom : TComponent;aFileName :string='');
  208. var
  209. FrmStrings : TStrings;
  210. begin
  211. RegisterClass(TPersistentClass(aFrom.ClassType));
  212. FrmStrings:=TStringlist.Create ;
  213. try
  214. if trim(aFileName)='' then FrmStrings.LoadFromFile( gsPathInfo+'/'+aFrom.Name+'.txt')
  215. else FrmStrings.LoadFromFile(aFileName);
  216. while aFrom.ComponentCount>0 do aFrom.Components[0].Destroy ;
  217. aFrom:=StringToComponent(FrmStrings.Text,aFrom)
  218. finally
  219. FrmStrings.Free;
  220. end;
  221. UnRegisterClass(TPersistentClass(aFrom.ClassType));
  222. end;
  223. 在FormCreate中调用ReadForm(self,…)。
  224. 这个方案没有第一个方案的限制,但是要求开发人员必须先完成一个完整的Form文件交给Form文件制作人员, Form文件的制作人员不能修改控件的name,不能添加或删除控件,而且必须保留开发人员给定所有事件处理函数,不能修改函数名。不过很多问题可以写一个Form编辑器来保证不出问题。
  225. 具体代码就不写了。
  226. 我想,肯定还有跟好的方案来解决动态窗体的问题,希望大家讨论。
  227. (以上代码使用Delphi6编写)
  228. 最后,我给出一个我实际项目中的有关动态窗体的函数的Unit
  229. {*****************************************
  230. 模块编号:J001DfmFunc
  231. 模块名称:Dfm窗体函数集单元
  232. 作者:刘爱军
  233. 建立日期:2002年12月2日
  234. 最后修改日期:
  235. 说明:本Unit包含了一些函数,用于根据Delphi窗体文件格式的文件动态创建窗体
  236. *******************************************}
  237. unit J001DfmFunc;
  238. interface
  239. uses
  240. Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  241. Dialogs, ExtCtrls, DBCtrls, Grids, DBGrids, Buttons, StdCtrls,
  242. ComCtrls,dbcgrids, buttonComps,Tabs,QryGlobal;
  243. type
  244. TAllComponentClass = Array of TPersistentClass;
  245. procedure InitClassType(ClassArray:TAllComponentClass);
  246. function ComponentToString(Component: TComponent): string;
  247. function StringToComponent(Value: string; Instance:TComponent): TComponent;
  248. procedure RegisterAllClasses(aAllCmpClass:TAllComponentClass);
  249. procedure UnRegisterAllClasses(aAllCmpClass:TAllComponentClass);
  250. function GetObjectString(list:TStrings;BegLine:Integer=0;TypeString:string=''):string;
  251. function LoadTextForm(FileName:String):TForm;
  252. function LoadTextForm2(FileName:String;out ErrMsg:string):TForm;
  253. procedure DeleteErrorLines(list:TStrings);
  254. procedure ReadForm(aFrom : TComponent;aFileName :string='');
  255. const
  256. RegisteredCompoentClassCount = 32;//数组大小
  257. var
  258. AllCmpClass : TAllComponentClass; //存放控件类
  259. implementation
  260. //初始化可以解析的类,可随需要增加
  261. procedure InitClassType(ClassArray:TAllComponentClass);
  262. begin
  263. SetLength(AllCmpClass,RegisteredCompoentClassCount);
  264. AllCmpClass[0] := TForm;
  265. AllCmpClass[1] := TGroupBox;
  266. AllCmpClass[2] := TPanel;
  267. AllCmpClass[3] := TScrollBox;
  268. AllCmpClass[4] := TLabel;
  269. AllCmpClass[5] := TButton;
  270. AllCmpClass[6] := TBitBtn;
  271. AllCmpClass[7] := TSpeedButton;
  272. AllCmpClass[8] := TStringGrid;
  273. AllCmpClass[9] := TImage;
  274. AllCmpClass[10] := TBevel;
  275. AllCmpClass[11] := TStaticText;
  276. AllCmpClass[12] := TTabControl;
  277. AllCmpClass[13] := TPageControl;
  278. AllCmpClass[14] := TTabSheet;
  279. AllCmpClass[15] := TDBNavigator;
  280. AllCmpClass[16] := TDBText;
  281. AllCmpClass[17] := TDBEdit;
  282. AllCmpClass[18] := TDBMemo;
  283. AllCmpClass[19] := TDBGrid;
  284. AllCmpClass[20] := TDBCtrlGrid;
  285. AllCmpClass[21] := TMemo;
  286. AllCmpClass[22] := TSplitter;
  287. AllCmpClass[23] := TCheckBox;
  288. AllCmpClass[24] := TEdit;
  289. AllCmpClass[25] := TListBox;
  290. AllCmpClass[26] := TComboBox;
  291. AllCmpClass[27] := TDateTimePicker;
  292. AllCmpClass[28] := TImageButton;
  293. AllCmpClass[29] := TTabSet;
  294. AllCmpClass[30] := TTreeView;
  295. AllCmpClass[31] := TListView;
  296. end;
  297. procedure RegisterAllClasses(aAllCmpClass:TAllComponentClass);
  298. var
  299. i:Integer;
  300. begin
  301. for i:=0 to RegisteredCompoentClassCount-1 do
  302. RegisterClass(aAllCmpClass[i]);
  303. end;
  304. procedure UnRegisterAllClasses(aAllCmpClass:TAllComponentClass);
  305. var
  306. i:Integer;
  307. begin
  308. for i:=0 to RegisteredCompoentClassCount-1 do
  309. UnRegisterClass(aAllCmpClass[i]);
  310. end;
  311. function ComponentToString(Component: TComponent): string;
  312. var
  313. BinStream:TMemoryStream;
  314. StrStream: TStringStream;
  315. s: string;
  316. begin
  317. BinStream := TMemoryStream.Create;
  318. try
  319. StrStream := TStringStream.Create(s);
  320. try
  321. BinStream.WriteComponent(Component);
  322. BinStream.Seek(0, soFromBeginning);
  323. ObjectBinaryToText(BinStream, StrStream);
  324. StrStream.Seek(0, soFromBeginning);
  325. Result:= StrStream.DataString;
  326. finally
  327. StrStream.Free;
  328. end;
  329. finally
  330. BinStream.Free
  331. end;
  332. end;
  333. function StringToComponent(Value: string; Instance:TComponent): TComponent;
  334. var
  335. StrStream:TStringStream;
  336. BinStream: TMemoryStream;
  337. begin
  338. StrStream := TStringStream.Create(Value);
  339. try
  340. BinStream := TMemoryStream.Create;
  341. try
  342. ObjectTextToBinary(StrStream, BinStream);
  343. BinStream.Seek(0, soFromBeginning);
  344. Result := BinStream.ReadComponent(Instance);
  345. finally
  346. BinStream.Free;
  347. end;
  348. finally
  349. StrStream.Free;
  350. end;
  351. end;
  352. function GetObjectString(list:TStrings;BegLine:Integer=0;TypeString:string=''):string;
  353. var
  354. i,iBegCount,iEndCount:Integer;
  355. ObjString,Line,ClassStr:String;
  356. begin
  357. iBegCount:=0;
  358. iEndCount:=0;
  359. ClassStr := Trim(UpperCase(TypeString));
  360. for i:=BegLine to list.Count-1 do
  361. begin
  362. line := UpperCase(list[i]);
  363. if Pos('OBJECT',line)>0 then
  364. begin
  365. if (TypeString=''or (Pos(': '+ClassStr,line)>0then
  366. Inc(iBegCount);
  367. end
  368. else if (iBegCount>iEndCount) and (trim(line)='END'then
  369. Inc(iEndCount);
  370. if iBegCount>0 then
  371. Result := Result + list[i] + #13#10;
  372. if (iBegCount>0and (iBegCount=iEndCount) then
  373. Exit;
  374. end;
  375. end;
  376. procedure DeleteErrorLines(list:TStrings);
  377. var
  378. i:Integer;
  379. line:String;
  380. begin
  381. if list.Count=0 then
  382. Exit;
  383. i:=0;
  384. while i<list.Count do
  385. begin
  386. line := Trim(list[i]);
  387. if Copy(line,1,2)='On' then
  388. list.Delete(i)
  389. else
  390. Inc(i);
  391. end;
  392. end;
  393. procedure ReadForm(aFrom : TComponent;aFileName :string='');
  394. var
  395. FrmStrings : TStrings;
  396. begin
  397. RegisterClass(TPersistentClass(aFrom.ClassType));
  398. FrmStrings:=TStringlist.Create ;
  399. try
  400. if trim(aFileName)='' then FrmStrings.LoadFromFile( gsPathInfo+'/'+aFrom.Name+'.txt')
  401. else FrmStrings.LoadFromFile(aFileName);
  402. while aFrom.ComponentCount>0 do aFrom.Components[0].Destroy ;
  403. aFrom:=StringToComponent(FrmStrings.Text,aFrom)
  404. finally
  405. FrmStrings.Free;
  406. end;
  407. UnRegisterClass(TPersistentClass(aFrom.ClassType));
  408. end;
  409. function LoadTextForm(FileName:String):TForm;
  410. var
  411. list:TStrings;
  412. FirstLine:String;
  413. iPos : Integer;
  414. Form : TForm;
  415. begin
  416. Result := nil;
  417. if FileExists(FileName)=False then
  418. Exit;
  419. Form := TForm.Create(Application);
  420. list := TStringList.Create;
  421. try
  422. list.LoadFromFile(FileName);
  423. if list.Count=0 then
  424. Exit;
  425. FirstLine := list[0];
  426. iPos := Pos(': ',FirstLine);
  427. if iPos = 0 then //找不到': ',格式不对
  428. Exit;
  429. list[0]:=Copy(FirstLine,1,iPos)+' TForm';
  430. DeleteErrorLines(list);
  431. StringToComponent(list.Text,Form);
  432. Result := Form;
  433. except
  434. Form.Free;
  435. Result := nil;
  436. end;
  437. list.Free;
  438. end;
  439. function LoadTextForm2(FileName:String;out ErrMsg:string):TForm;
  440. var
  441. list:TStrings;
  442. FirstLine:String;
  443. iPos : Integer;
  444. Form : TForm;
  445. begin
  446. Result := nil;
  447. if FileExists(FileName)=False then
  448. begin
  449. ErrMsg := '无效的文件名!';
  450. Exit;
  451. end;
  452. Form := TForm.Create(Application);
  453. list := TStringList.Create;
  454. try
  455. list.LoadFromFile(FileName);
  456. if list.Count=0 then
  457. Exit;
  458. FirstLine := list[0];
  459. iPos := Pos(': ',FirstLine);
  460. if iPos = 0 then //找不到': ',格式不对
  461. begin
  462. ErrMsg := '找不到'': '',文件格式不对';
  463. Exit;
  464. end;
  465. list[0]:=Copy(FirstLine,1,iPos)+' TForm';
  466. DeleteErrorLines(list);
  467. StringToComponent(list.Text,Form);
  468. Result := Form;
  469. except
  470. on e:exception do
  471. begin
  472. Form.Free;
  473. Result := nil;
  474. ErrMsg := '读入文件错误:'+e.Message;
  475. end;
  476. end;
  477. list.Free;
  478. end;
  479. initialization
  480. begin
  481. InitClassType(AllCmpClass);
  482. RegisterAllClasses(AllCmpClass);
  483. end;
  484. finalization
  485. UnRegisterAllClasses(AllCmpClass);
  486. end
原创粉丝点击