我应该将 delphi tframes 用于多页表单吗?
Posted
技术标签:
【中文标题】我应该将 delphi tframes 用于多页表单吗?【英文标题】:Should I use delphi tframes for multi-pages forms? 【发布时间】:2010-11-01 00:45:16 【问题描述】:我的应用程序中有一些表单具有不同的“状态”,具体取决于用户正在执行的操作;例如,在列出他的文件时,表单会在网格中显示有关该文件的一些数据,但如果他单击某个按钮,则该网格会被与其相关的图形替换。简单地说,表单中的控件取决于用户想要做什么。
当然,这样做的明显方法是根据需要显示/隐藏控件,这对小数字来说就像一个魅力,但是一旦每个状态达到 10/15+ 个控件(或者实际上超过 3 个状态),它就无法使用了。
我现在正在尝试使用 TFrame:我为每个状态创建一个框架,然后在我的表单上创建一个每个框架的实例,然后我只使用 Visible 显示我想要的那个 - 同时拥有它上面的一些控件,在任何框架之外,因为它们都共享它们。
这是做我想做的事情的正确方法,还是我在此过程中错过了什么?我以为我只能创建一个 tframe 实例,然后选择在其中显示哪个实例,但它看起来不是那样的。
谢谢
【问题讨论】:
【参考方案1】:看起来框架是这种情况的绝佳选择。我想补充一点,您可以使用 Base Frame 和 Visual Inheritance 来创建通用界面。
第二部分:你设计一个像表单一样的框架,但你像一个控件一样使用它,很少有限制。请注意,您可以轻松地使用 Create/Free 而不是 Show/Hide。哪个更好取决于它们的资源占用程度。
【讨论】:
【参考方案2】:有一种更好的方法来处理几乎不会占用那么多内存的帧。动态创建框架可能是一个非常优雅的解决方案。以下是我过去的做法。
在表单上,添加一个属性和一个设置器来处理它在表单上的放置:
TMyForm = class(TForm)
private
FCurrentFrame : TFrame;
procedure SetCurrentFrame(Value : TFrame);
public
property CurrentFrame : TFrame read FCurrentFrame write SetCurrentFrame;
end;
procedure TMyForm.SetCurrentFrame(Value : TFrame)
begin
if Value <> FCurrentFrame then
begin
if assigned(FCurrentFrame) then
FreeAndNil(FCurrentFrame);
FCurrentFrame := Value;
if assigned(FCurrentFrame) then
begin
FCurrentFrame.Parent := Self; // Or, say a TPanel or other container!
FCurrentFrame.Align := alClient;
end;
end;
end;
然后,要使用它,您只需将属性设置为框架的已创建实例,例如在 OnCreate 事件中:
MyFrame1.CurrentFrame := TSomeFrame.Create(nil);
如果您想摆脱框架,只需将 nil
分配给 CurrentFrame 属性:
MYFrame1.CurrentFrame := nil;
效果非常好。
【讨论】:
+1,但请注意,代码本身会导致内存泄漏。除非 MYFrame1.CurrentFrame 在 MYFrame1 被销毁之前或被明确设置为 nil ,否则它不会被释放。最好将 MyFrame1 作为所有者传递给构造函数,利用 VCL 中的自动生命周期管理。 @mghie:控件的父级 IIRC 也接管了生命周期管理,因此 Tim 的代码中应该没有内存泄漏。 实际上,我认为如果不是调用 TSomeFrame.Create(nil) 而是调用 TSomeFrame.Create(Self) 会使窗体成为框架的所有者,并且窗体会在它释放时释放它被自己摧毁。或者,您可以在 OnDestroy 事件中将 CurrentFrame 设置为 nil。从一帧更改到另一帧不会导致内存泄漏,因为设置器会在将 FCurrentFrame 设置为另一帧之前释放它。 现在我已经查看了一些旧代码,我个人使用了框架接口。因此,当 FCurrentFrame 设置为 nil 时,帧会自动释放。但是,这比我上面的示例更先进。 @Ulrich:感谢您的评论,看来您是对的,因为 TWinControl.Destroy 确实在其所有控件上调用了 Destroy。它有点复制 TComponent.DestroyComponents 所做的事情。我不知道为什么控件的处理方式与其他组件不同,是吗?无论如何,只要在新框架设置为活动时立即释放旧框架,答案中的代码就不会受到内存泄漏的影响。【参考方案3】:我有句话要告诉你:TFrameStack。顾名思义。
它有几个方法:PushFrame(AFrame)、PopFrame、PopToTop(AFrame)、PopToTop(Index)、 和一些属性: StackTop;帧[索引:整数];数;
应该是不言自明的。StackTop 的 Frame 是可见的。在执行 Back/Previous 之类的操作时,您不需要知道当前帧之前的帧是什么 :) 创建 Frame 时,您可以一次性创建并推送它 FrameStack.Push(TAFrame.Create) 等,创建它调用 BeforeShow proc 并使其可见,返回其在堆栈中的索引:)
但它确实严重依赖从共同祖先那里继承框架。这些框架(在我的案例中)都有程序:BeforeShow;免费前;隐藏之前;可见之前。 这些在 push、pop 和 top 期间由 FrameStack 对象调用;
从您的主窗体中,您只需要访问 FrameStack.Stacktop.whatever。我将我的 Stack 设为全局 :) 所以从其他对话框/窗口等访问它真的很容易。
另外不要忘记创建一个 Free 方法覆盖以在应用程序关闭时释放堆栈中的所有帧(如果所有者为 nil) - 您无需显式跟踪它们的另一个优点:)
创建 TFrameStack List 对象只需要少量工作。在我的应用程序中工作就像做梦一样。
天宝
【讨论】:
【参考方案4】:我还使用了@Tim Sullivan 描述的方法,并添加了一些内容。在每一帧中,我定义了帧初始化的过程——设置其组件的默认属性。
TAnyFrame = class(TFrame)
public
function initFrame() : boolean; // returns FALSE if somesthing goes wrong
...
end;
在创建框架后,我调用此过程。
aFrame := TAnyFrame.Create(nil);
if not aFrame.initFrame() then
FreeAndNil(aFrame)
else
... // Associate frame with form and do somthing usefull
此外,当您更改可见帧时,不必破坏前一个帧,因为它可以包含有用的数据。想象一下,您在第一帧/页面中输入了一些数据,然后转到下一个,然后决定再次更改第一页上的数据。如果您破坏前一帧,您将丢失其中包含的数据并需要恢复它们。解决方案是保留所有已创建的框架,仅在必要时创建新框架。
page_A : TFrameA;
page_B : TFrameB;
page_C : TFrameC;
current_page : TFrame;
// User click button and select a frame/page A
if not assigned(page_A) then begin
// create and initialize frame
page_A := TFrameA.Create(nil);
if not page_A.initFrame() then begin
FreeAndNil(page_A);
// show error message
...
exit;
end;
// associate frame with form
...
end;
// hide previous frame
if assigned(current_page) then
current_page.hide();
// show new page on the form
current_page := page_A;
current_page.Show();
【讨论】:
以上是关于我应该将 delphi tframes 用于多页表单吗?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Delphi 的 TFrame 上模拟 OnDestroy 事件?