基于 RTTI 的 TFrame 的创建和管理

2024-05-14 11:20
文章标签 创建 管理 rtti tframe

本文主要是介绍基于 RTTI 的 TFrame 的创建和管理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

缘由

在大型的 Delphi 程序开发中,界面会有非常多不一样的窗口。最原始的设计方式是在一个 Form 里面,堆叠一大堆的 Panel,每个 Panel 上摆放不同的控件。运行期需要显示什么界面,就让对应的 Panel 显示出来。这样做的问题是,设计期所有的界面元素都堆在一个窗口里面,控件太多互相覆盖,完全没法通过可视化的拖拉来摆放控件达到想要的视觉效果。

好一点的办法是每个界面开一个 TForm(对应运行期就是一个 Form 窗口),每个 Form 里面设计一个界面。需要换界面就显示不同的 Form。这样也符合【模块化】设计的模式。

上述做法的问题是:

A. Form 占用比较多的资源。几百个 Form 消耗的资源比较多。

B. 现在流行单窗口显示,仅仅是在一个 Form 里面换界面,而不是弹窗式的多窗口显示。单窗口显示的有点是避免了应用程序弹出一大堆窗口,用户失去当前操作焦点的问题。

解决上述问题的办法:

使用 TFrame 作为界面设计的容器。在 Frame 上面摆放视觉控件。一个 Frame 就是一个界面模块。

这样一来,程序就变成了有一个主窗体,这里我把它叫做 Form1,在这个 Form1 里面,按照当前用户操作需要,显示不同的 Frame1,Frame2...

更进一步的问题

问题来了,大型程序如果有几百个 Frame,那这个 Form1 所在的单元 Unit1 就需要引用(Delphi 的语法:uses)这几百个 Frame 的单元的名字。光那个 uses 后面的单元名字就一大堆,而且,有了这样的引用,一旦那个单元不在当前的工程项目里面(可能中途做了修改或者删除),就会连编译都通不过。总之就是管理起来很麻烦。

针对上述问题的一种解决方案

目标:

针对几百上千个代表不同界面的 Frame,呈现这些 Frame 模块的主 Form 不需要引用所有的 Frame 对应的单元文件。也不需要知道每个 Frame 的类型比如 TFrame1, TFrame2...等等。代码只需要使用每个 Frame 的类型名称的字符串,就可以把这个 Frame 显示到界面上。即便这个 Frame 已经不存在,也不会编译或者运行出错,仅仅是不能显示出来而已。

实现方式

1. 采用 Delphi 的 RTTI,可以通过类或者对象的方法的字符串名称,获得该方法的指针,并调用该方法。因此,我们就可以在没有引用这个 TFrame1 的情况下,调用它的 Create 方法。

2. 每个 TFrame1, TFrame2 都注册类型。这样就可以通过 FindClass 函数获得它的类类型。

3. 可能有一些 Frame 在程序里需要同时显示多个不同的实例,因此,需要使用一个 List 把它管理起来。因为相同类型的多个不同实例的对象需要有不同的对象名字,所以可以用名字作为索引来管理。因此这里可以使用 TDictionary 来管理。

代码如下

unit UFrameFactory;
{--------------------------------------------------------------------------使用 RTTI,输入 Frame 的字符串名字和 Parent,把它显示到 Parent 上面。1. Frame 必须在自己的单元的初始化部分 initialization 里面 RegisterClass 自己的类。2. 返回这个 Frame 的实例;3. 如果一个 Frame 需要同时有多个实例,用 TDictionary 管理。关于 TDictionary 管理这些 Frame 需要实现的功能:1. 对象缓冲,如果已经创建,下次需要,直接取出来,不用再次创建;2. 同一个 TFrame 多实例:如果需要同时两个,需要能够保存两个,使用名称来区别?可以给不同的 Tag 值来区别;3. 生命周期管理 -- Frame 创建时给它的 Owner 赋值 Application,最终由 Application 来管理。pcplayer 2024-5-13
-----------------------------------------------------------------------------}
interfaceuses  System.SysUtils, System.Variants, System.Classes, System.Generics.Collections,Vcl.Graphics,Vcl.Controls, Vcl.Forms, RTTI;typeTFrameManager = classprivateFList: TDictionary<string, TFrame>;class var FFrameManager: TFrameManager;function FindFrame(const FrameName, FrameTypeName: string): TFrame;function TheSameType(const AFrame: TFrame; const FrameTypeName: string): Boolean;class function GetFrameManager: TFrameManager; static;publicconstructor Create;destructor Destroy;function ShowFrame(const AParent: TWinControl; const FrameName, FrameTypeName: string): TFrame;procedure ReleaseFrame(const FrameName: string);class property FrameManager: TFrameManager read GetFrameManager;end;function CreateShowFrame(const AParent: TWinControl; const FrameName: string): TFrame;implementationfunction CreateShowFrame(const AParent: TWinControl; const FrameName: string): TFrame;
varCtx: TRttiContext;AType: TRttiType;AMethod: TRttiMethod;AValue: TValue;AClass: TClass;
begin//使用 RTTI,不需要引用该类所在单元,仅仅用类的字符串名称就可以。Result := nil;AClass := FindClass(FrameName);//其实,这里因为我们是只针对 TFrame 的,这里可以直接类型转换 AClass 为 TFrame 然后调用 TFrame.Create//而无需使用 RTTI 去查 Create 方法的指针。{if not (AClass is TFrame) then Exit;Result := TFrame(AClass).Create(Application);}Ctx := TRttiContext.Create;tryAType := Ctx.GetType(AClass);if not Assigned(AType) then Exit;AMethod := AType.GetMethod('Create');if not Assigned(AMethod) then Exit;AValue := AMethod.Invoke(AClass, [Application]);if AValue.IsObject thenbeginif not (AValue.AsObject is TFrame) then Exit;Result := TFrame(AValue.AsObject);Result.Parent := AParent;Result.Align := alClient;Result.BringToFront;end;finallyCtx.Free;end;
end;{ TFrameManager }constructor TFrameManager.Create;
beginFList := TDictionary<string, TFrame>.Create;
end;destructor TFrameManager.Destroy;
beginFList.Free;
end;function TFrameManager.FindFrame(const FrameName,FrameTypeName: string): TFrame;
varAFrame: TFrame;
beginResult := nil;if Self.FList.ContainsKey(FrameName) thenbeginif not Self.FList.TryGetValue(FrameName, AFrame) then Exit;if Self.TheSameType(AFrame, FrameTypeName) then Result := AFrame;end;
end;class function TFrameManager.GetFrameManager: TFrameManager;
beginResult := FFrameManager;
end;procedure TFrameManager.ReleaseFrame(const FrameName: string);
varAFrame: TFrame;
beginif Self.FList.TryGetValue(FrameName, AFrame) thenbeginFreeAndNil(AFrame);end;
end;function TFrameManager.ShowFrame(const AParent: TWinControl; const FrameName,FrameTypeName: string): TFrame;
beginResult := Self.FindFrame(FrameName, FrameTypeName);if not Assigned(Result) thenbeginif Self.FList.ContainsKey(FrameName) thenbeginraise Exception.Create('同名但不同类型的Frame已经存在!');end;Result := CreateShowFrame(AParent, FrameTypeName);Result.Name := FrameName;Self.FList.Add(FrameName, Result);end;with Result dobeginParent := AParent;Align := alClient;BringToFront;end;
end;function TFrameManager.TheSameType(const AFrame: TFrame;const FrameTypeName: string): Boolean;
varAClass: TClass;
beginResult := False;AClass := FindClass(FrameTypeName);Result := (AFrame is AClass);
end;initializationTFrameManager.FFrameManager := TFrameManager.Create;finalizationTFrameManager.FFrameManager.Free;end.

如何使用

在设计期,我们可以创建很多不同的 Frame。在主窗体(Form1)里面,需要显示某个 Frame 的地方,调用它的代码很简单,只需要一行代码就搞定:

procedure TForm1.Button4Click(Sender: TObject);
beginTFrameManager.FrameManager.ShowFrame(Panel3, 'Frame1A', 'TFrame1');
end;procedure TForm1.Button5Click(Sender: TObject);
beginTFrameManager.FrameManager.ShowFrame(Panel4, 'Frame1B', 'TFrame1');
end;procedure TForm1.Button6Click(Sender: TObject);
beginTFrameManager.FrameManager.ShowFrame(Panel5, 'Frame2A', 'TFrame2');
end;

上面的代码演示了:

1. TFrame1 并排显示了2个实例。

2. TFrame2 显示了一个实例。

总结一下

1. 通过上述方式,可以简化大型应用程序的代码,减少出错机会。

2. 其实用不着 RTTI,虽然我的代码里面使用了 RTTI。

这篇关于基于 RTTI 的 TFrame 的创建和管理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/988605

相关文章

解决IDEA使用springBoot创建项目,lombok标注实体类后编译无报错,但是运行时报错问题

《解决IDEA使用springBoot创建项目,lombok标注实体类后编译无报错,但是运行时报错问题》文章详细描述了在使用lombok的@Data注解标注实体类时遇到编译无误但运行时报错的问题,分析... 目录问题分析问题解决方案步骤一步骤二步骤三总结问题使用lombok注解@Data标注实体类,编译时

MySQL分表自动化创建的实现方案

《MySQL分表自动化创建的实现方案》在数据库应用场景中,随着数据量的不断增长,单表存储数据可能会面临性能瓶颈,例如查询、插入、更新等操作的效率会逐渐降低,分表是一种有效的优化策略,它将数据分散存储在... 目录一、项目目的二、实现过程(一)mysql 事件调度器结合存储过程方式1. 开启事件调度器2. 创

mysql外键创建不成功/失效如何处理

《mysql外键创建不成功/失效如何处理》文章介绍了在MySQL5.5.40版本中,创建带有外键约束的`stu`和`grade`表时遇到的问题,发现`grade`表的`id`字段没有随着`studen... 当前mysql版本:SELECT VERSION();结果为:5.5.40。在复习mysql外键约

Window Server创建2台服务器的故障转移群集的图文教程

《WindowServer创建2台服务器的故障转移群集的图文教程》本文主要介绍了在WindowsServer系统上创建一个包含两台成员服务器的故障转移群集,文中通过图文示例介绍的非常详细,对大家的... 目录一、 准备条件二、在ServerB安装故障转移群集三、在ServerC安装故障转移群集,操作与Ser

Window Server2016 AD域的创建的方法步骤

《WindowServer2016AD域的创建的方法步骤》本文主要介绍了WindowServer2016AD域的创建的方法步骤,文中通过图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习价... 目录一、准备条件二、在ServerA服务器中常见AD域管理器:三、创建AD域,域地址为“test.ly”

高效管理你的Linux系统: Debian操作系统常用命令指南

《高效管理你的Linux系统:Debian操作系统常用命令指南》在Debian操作系统中,了解和掌握常用命令对于提高工作效率和系统管理至关重要,本文将详细介绍Debian的常用命令,帮助读者更好地使... Debian是一个流行的linux发行版,它以其稳定性、强大的软件包管理和丰富的社区资源而闻名。在使用

Python在固定文件夹批量创建固定后缀的文件(方法详解)

《Python在固定文件夹批量创建固定后缀的文件(方法详解)》文章讲述了如何使用Python批量创建后缀为.md的文件夹,生成100个,代码中需要修改的路径、前缀和后缀名,并提供了注意事项和代码示例,... 目录1. python需求的任务2. Python代码的实现3. 代码修改的位置4. 运行结果5.

使用IntelliJ IDEA创建简单的Java Web项目完整步骤

《使用IntelliJIDEA创建简单的JavaWeb项目完整步骤》:本文主要介绍如何使用IntelliJIDEA创建一个简单的JavaWeb项目,实现登录、注册和查看用户列表功能,使用Se... 目录前置准备项目功能实现步骤1. 创建项目2. 配置 Tomcat3. 项目文件结构4. 创建数据库和表5.

使用SpringBoot创建一个RESTful API的详细步骤

《使用SpringBoot创建一个RESTfulAPI的详细步骤》使用Java的SpringBoot创建RESTfulAPI可以满足多种开发场景,它提供了快速开发、易于配置、可扩展、可维护的优点,尤... 目录一、创建 Spring Boot 项目二、创建控制器类(Controller Class)三、运行

JAVA中整型数组、字符串数组、整型数和字符串 的创建与转换的方法

《JAVA中整型数组、字符串数组、整型数和字符串的创建与转换的方法》本文介绍了Java中字符串、字符数组和整型数组的创建方法,以及它们之间的转换方法,还详细讲解了字符串中的一些常用方法,如index... 目录一、字符串、字符数组和整型数组的创建1、字符串的创建方法1.1 通过引用字符数组来创建字符串1.2