DELPHI盒子
!实时搜索: 盒子论坛 | 注册用户 | 修改信息 | 退出
检举帖 | 全文检索 | 关闭广告 | 捐赠
技术论坛
 用户名
 密  码
自动登陆(30天有效)
忘了密码
≡技术区≡
DELPHI技术
移动应用开发
Web应用开发
数据库专区
报表专区
网络通讯
开源项目
论坛精华贴
≡发布区≡
发布代码
发布控件
文档资料
经典工具
≡事务区≡
网站意见
盒子之家
招聘应聘
信息交换
论坛信息
最新加入: mulancc
今日帖子: 0
在线用户: 2
导航: 论坛 -> DELPHI技术 斑竹:liumazi,sephil  
作者:
男 dalas (dalas) ▲▲▲▲▲ -
普通会员
2020/12/16 10:49:15
标题:
动态创建的窗口自销毁,这算不算真正销毁了 浏览:956
加入我的收藏
楼主: 窗口关闭事件
Action:=caFree;

仍能访问窗口的类名,assigned也判断为对项存在
此帖子包含附件:dalas_2020121610492.rar 大小:2.47M
----------------------------------------------
-
作者:
男 grjs_2004 (grjsITname) ★☆☆☆☆ -
盒子活跃会员
2020/12/16 10:58:24
1楼: FreeAndNil(FormName);//这样才是真正的销毁
----------------------------------------------
Everyone will to do best!
作者:
男 wang_80919 (Flying Wang) ▲▲▲▲▲ -
普通会员
2020/12/16 11:19:50
2楼: 事件 是对象自己的代码。
对象说,请问,你打算 怎么 关闭?
caFree,就是 等会,我自己销毁自己。
其他的,不销毁。
但是,关键是 等会。

然后 事件里头,他不知道 你外头 还有 一个 保存 我 地址的 指针。
我 自己销毁了。也不可能帮你 修改你的指针啊。
地址 还在,只是 我不干了。88
----------------------------------------------
(C)(P)Flying Wang
作者:
男 vclclx (vclclx) ▲▲▲▲▲ -
注册会员
2020/12/16 11:33:59
3楼: Button1Click里建了个TForm(不是TForm1),TForm1.FormClose里指定了caFree。不知道你倒腾两种对象是在干嘛。
----------------------------------------------
-
作者:
男 dalas (dalas) ▲▲▲▲▲ -
普通会员
2020/12/16 11:50:56
4楼: @wang_80919 (Flying Wang)
Show 他不干了,但读他的ClassName仍然会干,Assigned也判断不准确。
刚才再试了下,Tag属性也可以读取。
暂时就用这个土办法,关闭动态窗口时,先把Tag赋值-1,外头再判断Tag的值,不用Assigned了。

@vclclx (vclclx)
再看看TForm1的Create事件代码,就知道是在干嘛了
----------------------------------------------
-
作者:
男 vclclx (vclclx) ▲▲▲▲▲ -
注册会员
2020/12/16 11:56:02
5楼: Delphi在堆中分配对象的内存,释放对象后,内存归还给操作系统,但是在归还时它不负责擦除数据。你自己保存了对象,相当于指针,指向堆中的内存,当你再次访问时,那个内存由于没被擦除,所以数据还在,这就是为什么tag还是那个值,我赋值了Caption,也是一样的。
----------------------------------------------
-
作者:
男 dalas (dalas) ▲▲▲▲▲ -
普通会员
2020/12/16 11:59:25
6楼: @ vclclx (vclclx)

是的,我就是在疑惑,Free后,那片内存是否还仍然由自己程序占着,还是归还系统了。如果归还系统了,Assigned还判断为真
----------------------------------------------
-
作者:
男 vclclx (vclclx) ▲▲▲▲▲ -
注册会员
2020/12/16 12:00:31
7楼: Assigned,英文是“指定”的意思,你给变量指定了值,所以Assigned判断结果为真。
你让对象自己free,但是对象不会去给你的变量清空为nil,所以Assigned依然是true。
----------------------------------------------
-
作者:
男 sxqwhxq (步惊云) ★☆☆☆☆ -
普通会员
2020/12/16 12:04:31
8楼: 在vcl中用freeandnil,在fmx/android/ios中用disposeof
使用前先判断是否为空
----------------------------------------------
-
作者:
男 wang_80919 (Flying Wang) ▲▲▲▲▲ -
普通会员
2020/12/16 12:32:07
9楼: 当我销毁我自己的时候,只是告诉系统,我走了,我的数据 系统 你爱咋咋地。
结果,系统说, 暂时你的数据 我不清除。没人要用你的地址。

结果 你 写代码 判断 tag 就还是能读取到。

假如 我 走了,系统急用内存。那么 你就会读取到其他数据。

另外  5楼 7楼解答正确。

楼主应该好好学习 计算机原理 里的  RAM 工作机制。

然后 学习 啥叫 变量 变量和 RAM 的关系。
啥叫 地址, 地址 和 RAM 的关系。
啥叫 变量的地址。
啥叫 变量的值的地址。
啥叫 对象。
啥叫 OOP。


类 他要占据 一块 内存区域。 

对象,他也要占据 一块 内存 区域。

当 对象 := 类.Create 的时候。
就是 分配 一个 内存区域给 对象。
执行 类的所在内存的 Create 函数。
目的只是 初始化 对象申请的这块内存而已。
如果 你 Create 里啥也不写。只是 不做 过多的初始化。
但是 依然有一些初始化操作被执行了。比如  一些 成员 被 赋予了 初值。
比如 成员是是一个对象的。他的初值就是 nil。

但是 如果 是 全局 变量。临时变量,他们都是无初值的。也就是 可能不是nil。
但是 类的成员,一定被赋予了 初值。


你的 Create 代码 可以 继续 做 初始化 等操作。

这些 代码 都存放在 类的 内存块中。

但是 他们 执行 之后。 对象 的内存块 就 被你修改好了。

所谓 对象的 销毁,就是 对象 执行了 类的 Destroy 函数。

但是 内存块 还在。系统还没来得及收回呢。

但是 你将 所有对象都销毁了。类 还是要继续占据 他的那块内存的。


当 对象 被销毁了,你继续执行 对象.某方法。
那么 只要 系统没收回内存。 那么 方法 是有可能 执行成功的。
但是  对象 基本上 都是 复杂的。
他 自己 也有 很多 成员。那些 成员 也被销毁了。
他们 的内存 可能已经被系统 用了。

这种 复杂程度,导致 你的 某方法 最终发生了错误。
----------------------------------------------
(C)(P)Flying Wang
作者:
男 dalas (dalas) ▲▲▲▲▲ -
普通会员
2020/12/16 12:48:12
10楼: 嗯嗯,要好好学习。
Delphi我是业余的,我的工作跟Delphi没有半毛钱关系,只是很多年前被朋友带着入坑,学一点偶尔写个小程序用用。
有时工作上需要处理大量数据,手工处理很麻烦,就自己写个小程序处理下,懒人的做法,呵呵
----------------------------------------------
-
作者:
男 wang_80919 (Flying Wang) ▲▲▲▲▲ -
普通会员
2020/12/16 12:49:26
11楼: 再说一遍。你应该 雇人。而不是自己解决。
失业率 已经 很高了,你居然 不考虑 给别人活路。

或者 你好好学习成为一个 程序员(同时也可以做别的工作)。 或者 雇人。
----------------------------------------------
(C)(P)Flying Wang
作者:
男 dalas (dalas) ▲▲▲▲▲ -
普通会员
2020/12/16 12:54:44
12楼: 我也是个打工的,在温饱线上挣扎,拿什么去雇人按此在新窗口浏览图片
----------------------------------------------
-
作者:
男 wang_80919 (Flying Wang) ▲▲▲▲▲ -
普通会员
2020/12/16 12:56:47
13楼: 你可以叫你老板雇人。

或者 你认认真真的学习成为一个 程序员。
然后 同时做你本职工作。写代码 可以是 业余的。

否则 就别搞开发。别让 其他人 失去 就业机会。
----------------------------------------------
(C)(P)Flying Wang
作者:
男 dalas (dalas) ▲▲▲▲▲ -
普通会员
2020/12/16 13:04:24
13楼: 我最初的目的,是想各个子窗口各自独立工作,同一个窗口类可能要创建多个,不要互相影响。同时,主窗口需要感知子窗口是否存在,如果存在,在主窗口关闭时,要通知各个子窗口保存数据。

虽然子窗口的Close事件里有写提醒保存的代码,但如果不先关闭子窗口,直接关闭主窗口,子窗口直接就消失了,没有执行保存数据代码。所以就搞了个数组,保存子窗口指针,哪知子窗口自己free了,主窗口里用Assigned还判断为真,无法得知子窗口真实状态。
----------------------------------------------
-
作者:
男 wang_80919 (Flying Wang) ▲▲▲▲▲ -
普通会员
2020/12/16 13:05:00
14楼: 作为一个 学过的人,你的 目前需求,MDI 可以解决。
不过,我们已经懒得用 MDI 了。没新意。
现在还用 MDI,人家顾客不买账。

算了,没基础。不值得讨论。
----------------------------------------------
(C)(P)Flying Wang
作者:
男 dalas (dalas) ▲▲▲▲▲ -
普通会员
2020/12/16 13:41:37
15楼: 感谢各位指导。

虽然没有达到最初“主窗口对子窗口创建后不理,让子窗口自己工作完成销毁自己的想法”,改为仍由主窗口销毁。

但仍学到不少东西,谢谢!!
----------------------------------------------
-
作者:
男 testerhook (CaptainHook) ★☆☆☆☆ -
普通会员
2020/12/16 14:22:56
16楼: 有很多办法可以实现楼主的目的。
比如消息,比如接口等等。
----------------------------------------------
-
作者:
男 dalas (dalas) ▲▲▲▲▲ -
普通会员
2020/12/16 14:23:16
16楼: 把数组改成全局,子窗口关闭时把自己从数组中删除,基本实现“子窗口创建后不理”了。
----------------------------------------------
-
作者:
男 dalas (dalas) ▲▲▲▲▲ -
普通会员
2020/12/16 14:25:33
17楼: @testerhook (CaptainHook)

消息似乎没用,试过主窗口关闭时,挨个搜索子窗口标题,然后给子窗口发送 wm_close,没有起到子窗口自己保存数据的作用(子窗口保存数据的代码是写在Close事件里)。最后才改用TForm 数组
----------------------------------------------
-
作者:
男 supermay (supermay) ★☆☆☆☆ -
盒子活跃会员
2020/12/16 16:32:45
18楼: 资料处理用python呀
----------------------------------------------
https://shop66090024.taobao.com/?spm=a313o.7775905.1998679131.d0011.6f6f2796Z7e3JX
作者:
男 bluestorm8 (bluestorm) ▲△△△△ -
注册会员
2020/12/16 16:53:46
19楼: procedure FreeAndNil(var Obj);
var
  Temp: TObject;
begin
  Temp := TObject(Obj);
  Pointer(Obj) := nil;
  Temp.Free;
end;
可以看出来,FreeAndNil(Form2)其实就等效于:
Form2.Free;
Form2 := nil;

如此而已。
----------------------------------------------
-
作者:
男 bluestorm8 (bluestorm) ▲△△△△ -
注册会员
2020/12/16 17:02:41
20楼: Form2.Free;
Form2只是一个指针,Form2.Free释放的是指针指向的数据,Form2这个指针本身并没有清零,所以Assigned(Form2)为True;
----------------------------------------------
-
作者:
男 dalas (dalas) ▲▲▲▲▲ -
普通会员
2020/12/16 17:31:47
21楼: 这个例子,能说明一些问题

如果同类子窗口只创建一个,用第二种方法,自销毁后可以清零,第一种方法不行。

同类窗口要创建多个,只能用第一方法,但要自己在主窗口清零数组
此帖子包含附件:dalas_20201216173135.rar 大小:2.46M
----------------------------------------------
-
作者:
男 pcplayer (pcplayer) ★☆☆☆☆ -
普通会员
2020/12/16 19:19:20
22楼: 这个问题居然这么多条讨论。

编程的时候要有个基本概念:
1. 实例;不管一个整数,还是一个对象,实例,就是这个东西生存的一段内存空间;

2. 变量:仅仅是指向那个实例所在的内存空间的一个指针;

var
  A: TForm2;

这里,A 是一个变量。你判断 Assigned(A) 仅仅是判断 A 有没有值。假设 A 有值,也就是A指向一段内存。

但是,这段内存完全可以是不含任何实例的空间。这个时候,即便 Assigned(A) 成功,A.xxx 的做法也是错误的,可能还会带来 AV 异常错误。

A.Free 仅仅是调用了实例的 Free 方法,对系统来说,这个实例不存在了,那片空间释放出来可以给别人用了。但 A 这个变量本身的值还在。这时候你做 Assigned(A) 去判断,自然 A 还在。

所以才有:FreeAndNil 函数。
----------------------------------------------
-
作者:
男 dalas (dalas) ▲▲▲▲▲ -
普通会员
2020/12/16 20:12:23
23楼: 受到 http://bbs.2ccc.com/topic.asp?topicid=590536 ;这个贴子的启发,
我现在改成:
1:子窗口数组为全局 pList:TArray<Pointer>
2:创建子窗口时 pList[High(pList)]:=TFrom2.Create(nil);
3:子窗口Close事件里 Action:=caFree; 同时把 pList 数组里自己指针设为 nil,这里我用了个循环,判断句柄是否相同。然后Move把数组后面的往前移一位,再 SetLength 数组下标减小1。

这样反复创建、关闭各个类型的子窗口,测试了近1小时,没再报错。基本上算是完美实现子窗口创建后不理,同时程序关闭还可以通知子窗口保存数据。


第1点,我之前是用 pList:TArray<TForm>,就会时不时报 invalid pointer opertor错误,搞得我一头雾水,找不出原因。这里要感谢 sxqwhxq 大大!!
----------------------------------------------
-
作者:
男 bluestorm8 (bluestorm) ▲△△△△ -
注册会员
2020/12/17 11:28:52
24楼: unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
  Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action:=caFree;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  with TForm.Create(Self) do
  begin
    OnClose := FormClose;
    Show;
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  I: Integer;
begin
  Memo1.Lines.Clear;
  for I := 0 to Screen.FormCount - 1 do
  begin
   if Screen.Forms[I] <> Self then
     Memo1.Lines.Add(Screen.Forms[I].ClassName);
  end;
end;

procedure TForm1.Button3Click(Sender: TObject);
var
  I: Integer;
begin
  for I := 0 to Screen.FormCount - 1 do
  begin
   if Screen.Forms[I] <> Self then
      Screen.Forms[I].Show;
  end;
end;

end.
----------------------------------------------
-
作者:
男 pcplayer (pcplayer) ★☆☆☆☆ -
普通会员
2020/12/17 17:45:06
25楼: 楼主,你的需求描述得有问题。

什么是【子】窗体?除开 MDI 这种很古老的父子窗体的格式以外,我不知道还有啥子窗体。

我把这个贴子来回翻了几遍,也还是不清楚你的需求。

假设新开发 Delphi VCL 程序,你就 New 一堆 VCL Forms 就好了。没有父子问题。唯一的一个与众不同的是第一个被程序创建的窗体,它是所谓的 MainForm。

你需要做的事不过是如何管理这一堆窗体。也就是创建完了,你需要知道它。

对于 Delphi 来说,变量有全局、局部。假设你这样创建:

var
  AForm: TForm2;
begin
  AForm := TForm2.Create(nil);
  AForm.Show;
end;

那么,假设你的程序运行时,你把这个 AForm 显示出来了,然后你把它关闭,默认设置下,它还在内存里面等待你重新召唤。但问题来了,你其它地方的代码,是看不到那个 AForm 变量的。因为它是局部变量,离开那个函数,AForm 这个变量就不存在了。

这种情况就是典型的内存泄露:实例还在,还没释放,但是,指向那个实例的指针搞丢了,没有了。

因此,你如果有很多对象实例需要全局访问管理,你就要定义全局的变量。比如你定义全局的 var Form1, Form2, Form3 ... 等等。

假设你不知道你的全局的变量有多少个,定义5个变量那就只能有5个,那么程序如果运行中超出5个怎么办?

这时候你就用一个变量来记住所有的变量。这个变量就是一个 List,其内部多半就是 TArray;

你直接用 TArray,或者 array of .. 或者 TList,TObjectList, TDictionary .. 等等。都是一个变量里面可以存多个变量的数据类型,来存放你的多个不管是 TForm 也好还是 TObject 也好的东西。你把所有指向这些东西的变量,都看成指针就好了。

啰嗦一句:

var AForm: TForm;
begin
  AForm := Form2;
   AForm.Show;
  AForm := Form3;
  AForm.Show;
end;

你试试上面这样的代码,你就知道那个变量 AForm 其实是可以随便指向某个 Form 的。

到这里,使用 Delphi 如何管理多个对象,基本的方法就这样了。其实,其它语言也差不多是这个样子了。

----------  分割线 ----------
但是,在 Delphi VCL 框架下面,Delphi 已经帮我们做了很多东西。比如那个保存多个对象的 List,在 VCL 框架里面已经有了,可以直接拿过来用。这个就是 Delphi 的 Owner 机制。比如,一个 Form1 可以是很多其它 Form 或者控件的 Owner。

最简单的情况就是,你拖几个按钮、Label, Memo 到 Form1 上面,这个 Form1 就是它们的 Owner,在 TForm1 内部的代码里面有一个 List 去记住它们,管理它们。这样当 Form1 被释放的时候,Form1 内部的代码会自动去释放它管理的这些控件。Delphi 帮我们做了这些代码,所以我们才可以不用写代码,直接关闭 Form 而无需写一堆释放 Button1, Button2, Edit1, Edit2 等等的代码。

那么,对于 Form 来说,Delphi 的默认代码是:

program test;

uses
  Vcl.Forms,
  Unit1 in 'Unit1.pas' {Form1},
  Unit2 in 'Unit2.pas' {Form2},
  Unit3 in 'Unit3.pas' {Form3};

{$R *.res}

begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm1, Form1);
  Application.CreateForm(TForm2, Form2);
  Application.CreateForm(TForm3, Form3);
  Application.Run;
end.

我专门为了写这段回复,做了一个简单的测试程序,有3个 Form;

Application.CreateForm(TForm2, Form2); 这句话,就是把 From2 这个变量放进了 Application 的管理的 List 里面。它的 Owner 因此就是 Application。

另外一种写法:

Form2 := TForm2.Create(Application);

这种写法,就是告诉 Form2 谁是它的 Owner;这样创建后,Application 的内部管理列表里面,也就有了这个 Form2 的指针。

对于 VCL 程序来说,Application 这个变量是全局的,只要声明了 Forms.pas 的地方,都可以直接使用它。

所以,默认情况下,Delphi 的 Application 这个变量,已经帮我们管理好了所有的 Form。

上代码:
前提代码:
procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := TCloseAction.caFree;
end;

procedure TForm3.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := TCloseAction.caFree;
end;

正式代码:
procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
begin
  Memo1.Lines.Add(Application.ComponentCount.ToString);
  Memo1.Lines.Add('MainForm: ' + Application.MainForm.Name);

  for I := 0 to Application.ComponentCount -1 do
  begin
    if (Application.Components[i] is TForm) then
    begin
      Memo1.Lines.Add(Application.Components[i].Name);
    end;
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  Form2.Show;
  Form3.Show;
end;

上面的代码,首先执行 Button2 把 Form2 和 Form3 显示出来。

然后,执行 Button1 看看有多少个 Form;
然后关闭 Form2,再执行 Button1;
然后关闭 Form3 再执行 Button1;

然后看结果:

Memo1
4
MainForm: Form1
Form1
Form2
Form3
3
MainForm: Form1
Form1
Form3
2
MainForm: Form1
Form1

----------
结果解释:
第一次能看到 Form1, Form2, Form3;
第二次只能看到 For1 和 Form3;
第三次只有 Form1;

结论:直接通过 Application 帮我们管理,就可以管理好这些 Form.

----------  分割线 ----------

具体到楼主的需求:程序退出时,可以通知这些 Form 执行保存。

我假设保存的代码在这些 Form 里面(实际上不应该在这里面,数据处理的代码应该单独放到一个 TDataModule 里面去);我假设你的每个 Form 都有一个相同的公共方法叫做 SaveMe;

在程序退出之前执行:

  for I := 0 to Application.ComponentCount -1 do
  begin
    if (Application.Components[i] is TForm2) then
    begin
      TForm2(Application.Components[i]).SaveMe;
    end
    else
    if (Application.Components[i] is TForm3) then
    begin
      TForm3(Application.Components[i]).SaveMe;
    end;
  end;

----------

上面的写法有个问题:要不停地判断。假设你有很多不同的 TForm 类型,代码写起来又没法又不优雅。

一个简单的办法就是你做一个 TMyForm,这个 TMyForm 有一个 SaveMe 方法,其它 Form 类型是从这个 TMyForm 继承。那么上述代码就可以不管你是 TForm1 还是 TForm2,我直接用 TMyForm(AForm).SaveMe; 来处理。

这个就是类继承的基本使用。

如果你的代码都写好了,都不是继承来的 Form。还有个类似的办法可以解决这个问题:使用 Delphi 的 Interface;你定义一个 Interface,它有一个方法叫做 SameMe;然后让需要的 TForm2, TForm3 都实现这个 Interface。

假设这个 Interface 的定义是:

TMyInterface = interface
  procedure SaveMe;
end;

那么就可以这样写:

  for I := 0 to Application.ComponentCount -1 do
  begin
    if (Application.Components[i] is TForm) then
    begin
      if supports( Application.Components[i], XXX, TMyInterface) then
      begin
        (Application.Components[i] as TMyInterface).SaveMe;
      end;
    end;
  end;

注意,上述 Supports 函数的使用方法,我这里是瞎写的,具体用法自己上网查。网上很多文章讲如何使用。

---------- 分割线 ----------
其实,数据操作的代码应该放在一个 DataModule 里面。甚至可以分为多个 DataModule,如果你的数据业务逻辑代码很复杂。这就好比小公司5个人,就5个人在一个办公室;大公司 500 人,就要分办公室,不是随便谁可以随便坐哪个办公室,而是做财务的都坐在一个办公室,做软件的都在一个办公室,做结构的,也都在另外一个办公室。这个就是所谓的模块化 --  为了复杂的代码方便管理。

而 Form,仅仅用于显示。仅仅是如何显示的代码,放在 TForm 的代码里面。
----------------------------------------------
-
作者:
男 dalas (dalas) ▲▲▲▲▲ -
普通会员
2020/12/17 17:49:03
26楼: 我的描述是没那么专业,我是半路出家,业余的delphi爱好者,我说的子窗口,是除了主窗口外,其他窗口都是子窗口,因为我每个子窗口就是一个模块,完全独立,各个“子窗口”之间不互相引用,且“子窗口”创建后,放到主窗口的一个TabSheet里
----------------------------------------------
-
作者:
男 pcplayer (pcplayer) ★☆☆☆☆ -
普通会员
2020/12/17 17:58:47
27楼: 楼上你说的这种,不就是一堆窗口么。是不是放到 TabSheet 里面,并不重要。

你自己使用一个全局的 List 来管理它们也没问题。

直接使用 Application 来管理它们,也不会有问题。

或者用你的 MainForm 也就是第一个窗体来管理它们,也不会有问题, 假设你的主窗口叫做 MyForm1,比如:

AForm := TForm99.Create(MyForm1);

至于把一个 Form 摆哪里,那个是 Parent 的事情。不是 Owner 的事情。这两个概念要区分清楚。

你有一堆窗体,互相不引用,但最终你的主窗体都要引用它们。或者,你也可以单独再做一个类,或者偷懒使用一个 DataModule 来引用管理它们,都可以。

至于说通知机制,就是各种函数调用。如果各种函数互相调用会带来互相的引用,而你又不想互相引用,因为尽量减少类之间的耦合也是非常有必要的,那么你就搞接口。有了接口,不需要引用那个 TForm99 的 unit,只需要在知道它的指针的时候,判断那个指针释放支持你的接口,支持的话,调用接口方法就可以。
----------------------------------------------
-
作者:
男 bluestorm8 (bluestorm) ▲△△△△ -
注册会员
2020/12/17 17:59:50
27楼: 如果“子窗口”创建后,放到主窗口的一个TabSheet里,那你用的应该用TFrame,而不是TForm
----------------------------------------------
-
作者:
男 pcplayer (pcplayer) ★☆☆☆☆ -
普通会员
2020/12/17 18:28:06
28楼: 楼上,用 TForm 还是用 TFrame 其实不重要。除非你非常在意系统资源消耗。
----------------------------------------------
-
作者:
男 dalas (dalas) ▲▲▲▲▲ -
普通会员
2020/12/17 20:18:04
29楼:
@pcplayer (pcplayer)

其实我原本是如图1,各个模块一个“子窗口”,当直接关闭主窗口时(有些人就是比较着急,觉得挨个关闭子窗口麻烦,直接关主窗口退出程序),各“子窗口”都会正常发生 OnClose 事件(保存数据的代码就写在 Close 事件里),数据可以正常保存。

但是经常在多个“子窗口”间换来换去(有时还要到处找子窗口在哪)比较麻烦。
就改成如图2,“子窗口”全部放到主窗口的一个TabSheet里,这样在各个“子窗口”切换比较方便。

问题也来了,如上面所说的,“子窗口”没关,直接关主窗口,这时,各个“子窗口”会直接消失,不会发生 Close 事件,数据就无法保存。

所以,我想在主窗口关闭时,通知“子窗口”关闭,保存数据。由于各个“子窗口”是自销毁,即关闭就自动销毁自己,所以我原本是没有保留“子窗口”的指针的,主窗口只负责创建“子窗口”,其他的一概不管。但现在需要通知“子窗口”保存数据了,所以我建了个 TForm 数组 FormList:TArray<TForm>,保留各“子窗口”,在主窗口 Close 事件里:
for i:=Low(FormList) to High(FormList) do
if Assigned(FormList[i]) then
  FormList[i].Close;
这样先挨个关闭“子窗口”,可以保存数据。

但由于“子窗口”是自销毁,使用过程中如果有关闭过个别“子窗口”,虽然销毁了,但 数组里的指针并没有清空,Assigned(FormList[i]) 永远为 True,所以就出现了我在“楼主”那里所说的问题。

后面大家各舒已见展开的讨论,也让我学到不少知识,再次表示感谢!

原因就是这样了,目前受到 sxqwhxq 的贴子的启发,我用 23 楼的方法,用 FormList:TArray<Pointer> 替换 FormList:TArray<TForm>,昨晚到现在,没再出什么问题,暂时看没什么问题。

刚刚,也试了下你25楼的办法,用 Application.Components[i] 替换数组的办法,也挺好,测试了一会,一切正常。好处是不用自己维护一个全局数组,更方便。

我在数组前,也试过想用 TObjectList,但总报错,可能是我不会用,汗。。。。
能用 List,肯定比用数组更好管理,更方便。
此帖子包含附件:
PNG 图像
大小:170.5K
----------------------------------------------
-
作者:
男 dalas (dalas) ▲▲▲▲▲ -
普通会员
2020/12/17 20:21:54
30楼: 这也是为何“子窗口”我没用TFrame,而是用TForm的原因,因为一开始本来就是一个一个独立的窗口,所以也不想再改成TFrame了。

实际上我现在把图1和图2两种方式,用一个选项来管理了,让用户自己选择要用图1或图2的方式,他们喜欢哪个就选哪个。
----------------------------------------------
-
作者:
男 pcplayer (pcplayer) ★☆☆☆☆ -
普通会员
2020/12/17 21:30:38
31楼: TObjectList 你要查 Help 啊。它有个属性:是否 Owner 所属的控件。

如果这个属性为 False,则 ObjectList.Delete(i) 把指针从它管理的列表里面去掉,对象实例不会被释放;如果是 True,则对象实例本身就被释放了,实例不存在了。如果这时候指针还在,当然会出错。

TList 不会管释放它管理的指针对应的实例。所以就不会出错。

你最后这个帖子,才把你的需求说清楚了。网上发贴提问,尽量多写点字说得清楚一点,别人也好帮你分析。否则大家都是猜,那就很难说猜到哪里了。
----------------------------------------------
-
作者:
男 dalas (dalas) ▲▲▲▲▲ -
普通会员
2020/12/17 22:13:54
32楼: 现在再爬了下楼,才发现24楼的 bluestorm8 (bluestorm) 的也是一个好办法,跟 Application.Components[i] 异曲同工
----------------------------------------------
-
作者:
男 bluestorm8 (bluestorm) ▲△△△△ -
注册会员
2020/12/18 12:08:26
33楼: Application.Components[i]只能发现Application.CreateForm所创建的Form,发现不了mainform创建的Form
----------------------------------------------
-
作者:
男 pcplayer (pcplayer) ★☆☆☆☆ -
普通会员
2020/12/19 11:38:42
34楼: 33楼,我前面写了那么大一段,算是白写了。
----------------------------------------------
-
作者:
男 roadrunner (roadrunner) ★☆☆☆☆ -
盒子活跃会员
2020/12/19 12:49:23
35楼: 自从delphi取消了ARC之后,不再有任何办法判断一个对象变量是否已经被free过。

如果你访问一个已经被free过的对象的内部属性,可能会抛异常,但不一定会抛,所以你用try来拦截异常也是无法确认是否free过的

总之,这是常规内存管理方案的一个死穴,这也是为什么JAVA, .NET这些新一代语言顶着这么巨大的性能损害也要搞自动内存管理。

对于DELPHI,C++这类常规语言来说,这是无解的,必须靠程序员自己心里追踪一切对象的释放,错误是不可避免的,唯一的办法就是死命地去DEBUG查错。
----------------------------------------------
-
作者:
男 emailx45 (emailx45) ▲▲▲△△ -
注册会员
2020/12/19 17:03:50
36楼: it's not necessary create any list for take care of your sub-forms
----------
for "Close" your sub-forms, dont use any procedure where the user
should be "questioned": Close or Not Close this form?


----------

frmFormSecond.COMPONENTS / COMPONENTSCOUNT  can be used to find your form desired!

----------
var
  frmFormSecond: TfrmFormSecond;

implementation

{$R *.dfm}

procedure TfrmFormSecond.btn_Creating_FormsClick(Sender: TObject);
var
  lMyNewForm: TForm;
begin
  lMyNewForm := TForm.Create(nil); // or (Self);
  try
    //
    // all components will be "automatically" destroyed
    //
    frmFormSecond.InsertComponent(lMyNewForm); // Component should be "no-name", or, "name-unique" <> others components on Form!!!
    //
    if not(lMyNewForm.Owner = nil) then
      lMyNewForm.Caption := 'Owner = ' + lMyNewForm.Owner.Name;
    //
    lMyNewForm.Show; // or ShowModal ... but...

  except
    // ... what do it?
  end;
end;

initialization

ReportMemoryLeaksOnShutdown := true; // verify basic "Memory Leaks"

finalization

end.
----------------------------------------------
The higher the degree, the greater the respect given to the humblest!
作者:
男 emailx45 (emailx45) ▲▲▲△△ -
注册会员
2020/12/20 1:09:27
37楼: up
----------------------------------------------
The higher the degree, the greater the respect given to the humblest!
作者:
男 speedbin (speedbin) ★☆☆☆☆ -
盒子活跃会员
2020/12/21 20:32:48
38楼: 天哪,想不到这玩意居然能讨论这么长,这不很简单么,拦截消息就行了。
正常关闭窗口会触发WM_SYSCOMMAND,cmdType为SC_CLOSE
当主窗口拦截到关闭消息,通知各个子窗口就行了,这么简单的事情居然搞这么复杂。

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
  private
    { Private declarations }
    procedure WMSysCommand(var Msg: TWMSysCommand); message WM_SYSCOMMAND;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses Unit2;

{ TForm1 }

procedure TForm1.WMSysCommand(var Msg: TWMSysCommand);
begin
  if Msg.CmdType = SC_CLOSE then
    ShowMessage('OK');
  inherited;
end;
----------------------------------------------
-
作者:
男 a07272 (Allen) ★☆☆☆☆ -
盒子活跃会员
2020/12/22 0:46:01
39楼: Wow 好不容易在這裡看到類似這樣存技術的討論,這個問題把一些潛水員都挖出來了,貢獻出不同的寫法,這樣的交流或許可以讓這個日漸凋零的DELPHI,可以延續....辛苦了各位
----------------------------------------------
-
作者:
男 jackalan (nVicen) ★☆☆☆☆ -
盒子活跃会员
2020/12/22 9:44:14
40楼: 学会用TLIST去管理你创建的窗口。
只要创建了就加入LIST,释放时从LIST移除。

至于前面老猫说的问题,你的确应该学习一下,就好比删除文件,系统只是标记这个文件已经删除,实际内容还在,但只要有数据存储需要用到空间,系统随时都可以覆盖这个区域。
----------------------------------------------
简单做人,认真做事。
作者:
男 pcplayer (pcplayer) ★☆☆☆☆ -
普通会员
2020/12/22 16:20:20
41楼: 35 楼,对于 Delphi 来说,要管理一个对象是否生死,是有办法的。

办法也不复杂,就是利用 Delphi 的接口计数机制。用这个机制,为自己创建的对象,做一个生存管理,无需去注意 Create 了就要去 Free,也不会有内存泄漏,想要用对象的时候,也不会用了一个已经释放的对象。
----------------------------------------------
-
作者:
男 bluestorm8 (bluestorm) ▲△△△△ -
注册会员
2020/12/23 10:47:15
42楼: 将TList<T>接口化:

unit uIList;

interface

uses System.Classes, System.Generics.Collections, System.Generics.Defaults;

type

  IList<T> = interface
    procedure Clear;
    function Add(const Value: T): Integer;
    function Remove(const Value: T): Integer;
    procedure Delete(Index: Integer);
    function IndexOf(const Value: T): Integer;
    procedure Sort(const AComparer: IComparer<T>);
    function GetEnumerator: TList<T>.TEnumerator;

    function GetCount: Integer;
    property Count: Integer read GetCount;

    function GetItem(Index: Integer): T;
    procedure SetItem(Index: Integer; Item: T);
    property Items[Index: Integer]: T read GetItem write SetItem; default;
  end;

  TListIntf<T> = class
    class function Create: IList<T>;
  end;

  TInterfacedList<T> = class(TList<T>, IList<T>)
   private
    const objDestroyingFlag = Integer($80000000);
    function GetRefCount: Integer; inline;
  protected
    [Volatile] FRefCount: Integer;
    class procedure __MarkDestroying(const Obj); static; inline;
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
    property RefCount: Integer read GetRefCount;

    function GetCount: Integer;
    function GetItem(Index: Integer): T;
    procedure SetItem(Index: Integer; Value: T);
  public
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;
    class function NewInstance: TObject; override;

    property Items[Index: Integer]: T read GetItem write SetItem; default;
    property Count: Integer read GetCount;
  end;

implementation

class function TListIntf<T>.Create: IList<T>;
begin
  Result := TInterfacedList<T>.Create;
end;

//----------

function TInterfacedList<T>.GetCount: Integer;
begin
  Result := inherited Count;
end;

function TInterfacedList<T>.GetItem(Index: Integer): T;
begin
  Result := inherited Items[Index]
end;

procedure TInterfacedList<T>.SetItem(Index: Integer; Value: T);
begin
  inherited Items[Index] := Value;
end;

function TInterfacedList<T>.GetRefCount: Integer;
begin
  Result := FRefCount and not objDestroyingFlag;
end;

class procedure TInterfacedList<T>.__MarkDestroying(const Obj);
var
  LRef: Integer;
begin
  repeat
    LRef := TInterfacedList<T>(Obj).FRefCount;
  until AtomicCmpExchange(TInterfacedList<T>(Obj).FRefCount, LRef or objDestroyingFlag, LRef) = LRef;
end;

procedure TInterfacedList<T>.AfterConstruction;
begin
// Release the constructor's implicit refcount
  AtomicDecrement(FRefCount);
end;

procedure TInterfacedList<T>.BeforeDestruction;
begin
  if RefCount <> 0 then
    System.Error(reInvalidPtr);
end;

// Set an implicit refcount so that refcounting during construction won't destroy the object.
class function TInterfacedList<T>.NewInstance: TObject;
begin
  Result := inherited NewInstance;
  TInterfacedList<T>(Result).FRefCount := 1;
end;

function TInterfacedList<T>.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  if GetInterface(IID, Obj) then
    Result := 0
  else
    Result := E_NOINTERFACE;
end;

function TInterfacedList<T>._AddRef: Integer;
begin
  Result := AtomicIncrement(FRefCount);
end;

function TInterfacedList<T>._Release: Integer;
begin
  Result := AtomicDecrement(FRefCount);
  if Result = 0 then
  begin
    // Mark the refcount field so that any refcounting during destruction doesn't infinitely recurse.
    __MarkDestroying(Self);
    Destroy;
  end;
end;

end.
----------------------------------------------
-
作者:
男 bluestorm8 (bluestorm) ▲△△△△ -
注册会员
2020/12/23 10:51:21
43楼: 使用接口化的IList(FormList不需要Free):
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
  Vcl.StdCtrls, uIList;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    Id: Integer;
    FormList: IList<TForm>;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  ReportMemoryLeaksOnShutDown := True;
  FormList := TListIntf<TForm>.Create;
  Position := poScreenCenter;
  Id := 2;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action:=caFree;
  FormList.Remove(TForm(Sender));
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Form: TForm;
begin
  Form := TForm.Create(Self);
  Form.OnClose := FormClose;
  FormList.Add(Form);
  Form.Name := 'Form' + Id.ToString;
  Inc(Id);
  Form.Show;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  Form: TForm;
begin
  Memo1.Lines.Clear;
  for Form in FormList do
  begin
     Memo1.Lines.Add(Form.Name + ': ' + Form.ClassName);
  end;
end;

procedure TForm1.Button3Click(Sender: TObject);
var
  Form: TForm;
begin
  for Form in FormList do
  begin
    Form.Show;
  end;
end;

end.
----------------------------------------------
-
作者:
男 bluestorm8 (bluestorm) ▲△△△△ -
注册会员
2020/12/23 11:02:36
44楼: DFM:

object Form1: TForm1
  Left = 0
  Top = 0
  Caption = 'Form1'
  ClientHeight = 199
  ClientWidth = 458
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  OnCreate = FormCreate
  PixelsPerInch = 96
  TextHeight = 13
  object Memo1: TMemo
    Left = 0
    Top = 0
    Width = 281
    Height = 199
    Align = alLeft
    Lines.Strings = (
      'Memo1')
    TabOrder = 0
    ExplicitHeight = 201
  end
  object Button1: TButton
    Left = 328
    Top = 48
    Width = 89
    Height = 25
    Caption = #21019#24314#23376#31383#21475
    TabOrder = 1
    OnClick = Button1Click
  end
  object Button2: TButton
    Left = 328
    Top = 88
    Width = 89
    Height = 25
    Caption = #26174#31034#31383#21475#31867#21517
    TabOrder = 2
    OnClick = Button2Click
  end
  object Button3: TButton
    Left = 328
    Top = 128
    Width = 89
    Height = 25
    Caption = 'Button3'
    TabOrder = 3
    OnClick = Button3Click
  end
end
----------------------------------------------
-
作者:
男 dalas (dalas) ▲▲▲▲▲ -
普通会员
2020/12/23 13:16:40
45楼: 楼上干货,收了,感谢!!
----------------------------------------------
-
作者:
男 dalas (dalas) ▲▲▲▲▲ -
普通会员
2020/12/23 13:21:46
46楼: @speedbin (speedbin)

其实主要讨论是实例的创建和释放,及生存期管理的各种方法,不是消息怎么通知的问题,哈哈
----------------------------------------------
-
作者:
男 speedbin (speedbin) ★☆☆☆☆ -
盒子活跃会员
2020/12/23 14:50:07
47楼: @dalas
当主窗体关闭的时候说:嘿,各位子窗体,我准备关闭了,你们赶紧保存数据。
既然子窗体没有onclose事件,你完全可以自己触发事件。
子窗体收到通知,然后完成保存工作和销毁过程。

另外,除了Delphi,windows程序设计,windows核心编程都要看看啊,不要局限于delphi一门语言。
----------------------------------------------
-
作者:
男 richard6688 (Ricgard) ★☆☆☆☆ -
盒子活跃会员
2020/12/26 14:54:47
48楼: 呵呵,我竟然看完这个贴了,也是今天闲的.
如果我没理解错的话,楼主的需求就是要每个子form 自己处理好保存数据。
楼主用的OnClose来处理,结果不能正确应对, 引来无数其他问题。
关闭时处理手尾,应该用OnCloseQuery事件,没处理好,可以拒绝退出。
正确使用事件,没有那么复杂。
----------------------------------------------
-
作者:
男 feiyanm (feiyanm) ▲▲▲△△ -
注册会员
2020/12/29 8:16:58
49楼: 需要处理大量数据,也可以直接用Web方式,比如使用MHS:
http://www.moonserver.cn/mhs/single-post.pp?id=68&catalog=1
数据处理用pascal代码写,给用户展示的内容也直接用脚本展示就好了。
----------------------------------------------
Delphi威武!千秋万代,一统江湖!Delphi威武!千秋万代,一统江湖!Delphi威武!千秋万代,一统江湖!Delphi威武!千秋万代,一统江湖!Delphi威武!千秋万代,一统江湖!Delphi威武!千秋万代,一统江湖!Delphi威武!千秋万代,一统江湖!我去WC吐一会儿去!
作者:
男 wr960204 (武稀松) ★☆☆☆☆ -
盒子活跃会员
2020/12/29 12:12:15
50楼: caFree窗口确实销毁了,但内存未必会还给系统,很大概率是留着后面重用。
另外变量也不会给你赋值成nil
所以assigned看来指针不是空,内存没回收给操作系统,所以很多数据大概率都可以读到
----------------------------------------------
武稀松http://www.raysoftware.cn
信息
登陆以后才能回复
Copyright © 2CCC.Com 盒子论坛 v2.1 版权所有 页面执行46.875毫秒 RSS