调用窗体方法时访问冲突
Access Violation While Calling Form's Method
我有一个表单 TForm1,我在其中保留了 cxSpreadsheet 组件。我还创建了一个名为 TTest 的 class。 TTest class 包含两种方法,一种用于加载数据,另一种用于保存数据。
当我在 TTest class 方法中调用 TForm1 class 的方法时,即使创建了组件,当我调用 Test.LoadMyData 方法时它也会出现访问冲突,它给 ATableView1 变量带来访问冲突。
我做错了什么?
type
TForm1 = class(TForm)
dxSpreadSheet1: TdxSpreadSheet;
procedure FormShow(Sender: TObject);
public
ATableView1 : TdxSpreadSheetTableView;
procedure Initilize;
procedure LoadData;
end;
TTest = class
public
procedure SaveMyData(MyValue : String);
procedure LoadMyData;
end;
var
Form1: TForm1;
Test: TTest;
implementation
{$R *.dfm}
procedure TForm1.FormShow(Sender: TObject);
begin
Form1 := TForm1.Create(Self);
Initilize;
Test := TTest.Create;
Test.LoadMyData;
end;
procedure TForm1.Initilize;
begin
ATableView1 := dxSpreadSheet1.Sheets[0] as TdxSpreadSheetTableView;
end;
procedure TForm1.LoadData;
begin
ATableView1.Cells[10,1].SetText('Test Application');
end;
procedure TTest.LoadMyData;
begin
Form1.LoadData;
end;
你搞得一团糟。 Delphi 方法对隐式 Self
对象(实例)进行操作。也称为方法调用的目标。如果我们用明确的 Self
编写您的代码,那么它看起来像这样:
procedure TForm1.FormShow(Sender: TObject);
begin
Form1 := TForm1.Create(Self);
Self.Initilize;
Test := TTest.Create;
Test.LoadMyData;
end;
procedure TForm1.Initilize;
begin
Self.ATableView1 := Self.dxSpreadSheet1.Sheets[0] as TdxSpreadSheetTableView;
end;
procedure TForm1.LoadData;
begin
Self.ATableView1.Cells[10,1].SetText('Test Application');
end;
procedure TTest.LoadMyData;
begin
Form1.LoadData;
end;
如您所见,您对 Self
和 Form1
的引用相当邪恶。请注意,Form1
全局变量是在 .dpr 文件中使用以下代码行实例化的:
Application.CreateForm(TForm1, Form1);
这最终导致 FormShow
在 Form1
上被调用。然后您覆盖 Form1
,现在有两个 TForm1
实例。您在 Self
上调用了 Initilize
,但随后在 Form1
上调用了 LoadData
。
整个事情一团糟。代码可能如下所示:
// remove global variable Test
procedure TForm1.FormShow(Sender: TObject);
var
Test: TTest;
begin
Initilize;
Test := TTest.Create;
try
Test.LoadMyData(Self);
finally
Test.Free;
end;
end;
procedure TForm1.Initilize;
begin
ATableView1 := dxSpreadSheet1.Sheets[0] as TdxSpreadSheetTableView;
end;
procedure TForm1.LoadData;
begin
ATableView1.Cells[10,1].SetText('Test Application');
end;
procedure TTest.LoadMyData(Form: TForm1);
begin
Form.LoadData;
end;
我们不使用全局变量,而是将表单引用作为参数传递。
一旦你掌握了这一点,你可能会考虑完全删除全局变量 Form1
。 IDE 在假设每个表单都只有一个实例的情况下创建此变量。但没有理由必须如此。就个人而言,我认为通过删除这些全局变量,您将在长期 运行 中受益。
正如 David 雄辩地指出的那样,这是你造成的混乱局面。他说 IDE 在项目文件中插入一行自动为您创建表单是正确的。所以自己创建它是多余的。
在此实例中,OnShow 处理程序可能是将一些测试数据添加到网格的正确位置,但我不会费心使用 TTest class 来执行此操作。也就是说,首先让逻辑正常工作,然后根据需要将内容移至另一个 class。我建议这样做是因为您似乎不太了解如何进行 OOD。我们都在那里!先把事情简单化。
我为 CodeRage 9 制作了一个您可能会感兴趣的视频。它叫做 "Have You Embraced Your Inner Software Plumber Lately?" 在 YouTube 上搜索它。它会让您对这个一般主题有一些了解(可能太多了)。
type
TForm1 = class(TForm)
dxSpreadSheet1: TdxSpreadSheet;
procedure FormShow(Sender: TObject);
public
ATableView1 : TdxSpreadSheetTableView;
procedure Initilize;
procedure LoadData;
end;
TTest = class
public
procedure SaveMyData(MyValue : String);
procedure LoadMyData;
end;
var
Form1: TForm1;
Test: TTest;
implementation
{$R *.dfm}
procedure TForm1.FormShow(Sender: TObject);
begin
//Form1 is nil, if I check before executing below statement.
Form1 := Self; //This statement make my code work. mmmhhhhhaaaa Love This Statement.
Initilize;
Test := TTest.Create;
Test.LoadMyData;
end;
在您的问题中,您遗漏了一个非常重要的难题。你在评论中提到过它,但我在这里重复一遍,因为它是你问题的直接触发因素。在评论中,您说表单的创建方式如下:
with TForm1.Create(Self) do
begin
try
ShowModal;
finally
Free;
end;
end;
您的问题是,在 ShowModal
的调用链中,您希望 Form1
被分配 专门给您刚刚创建的实例 。但显然你还没有做任何设置 Form1
.
您的 "solution" 是在 ShowModal
调用链中分配 Form1
。虽然这解决了您眼前的问题,但它远非正确,稍后我将解释原因。首先,我将展示可以完全避免该问题的简单解决方案(请注意,这不是一个完整的解决方案,因为您的代码存在的问题比您意识到的要多得多)。
解决办法,去掉和:
//This first line is the most important.
//It explicitly sets which variable must be assigned to the new form.
Form1 := TForm1.Create(Self);
try
Form1.ShowModal;
finally
Form1.Free;
end;
Vishal,试试吧。如果你这样做,你会看到它有效。希望我现在得到你的全部关注。起初你不相信,但也许你现在意识到我真的做理解正确.
那我为什么说你的 "solution" 错了呢?毕竟好像解决了问题....
Well, you said it yourself: you expected Form1
to be assigned at the start of FormShow
. You're right, it should be. By assigning Form1 := Self;
inside FormShow
you're just patching up the earlier mistake. Surely you agree it's better to fix the original mistake than to just patch over it later?
但是这里有一个更深层次的问题......我不确定你是否理解 "object instances" 和 "classes" 之间的区别。 (如果你这样做,那么请继续阅读接下来的几段以进行修改。)
您似乎希望在创建 TForm1
时自动将其分配给 Form1
变量。就好像您预期在任何时间点内存中只会有一个 TForm1
。但是 TForm1
是 class 类型;这意味着它通常定义 任意数量的相同类型的 对象实例的行为。每次您创建一个 TForm1
,它都是一个新的单独的表单实例。每个实例都可以有自己的变量分配给它。例如
JohnsForm1 := TForm1.Create(Self);
PaulsForm1 := TForm1.Create(Self);
考虑一下,如果您需要 2 个表单变量,您不正确的 "solution" 会发生什么情况?你会在 FormShow
方法中写什么? JohnsForm1 := Self;
或PaulsForm1 := Self;
?
当然你仍然可以选择一次只在内存中保留TForm1
的1个实例。但是 Delphi 无法自动知道这就是您的意图。所以你仍然应该像上面那样明确地做任何想要的任务。
我提到你的代码还有更严重的问题。这与上面关于对象实例和 class 类型的讨论有关。
你的TTest
class做了一堆不必要的假设:
- 它假定永远只有 1 个
TForm1
实例。
- 它假定表单在需要时始终可用。
- 并且它假定表单将分配给
Form1
变量。
所以,如果您需要 JohnsForm1
和 PaulsForm1
,您的代码将无法正常工作。
对 TTest.LoadMyData
和 TForm1.FormShow
的微小改动解决了这些问题。
//Write LoadMyData so it can be told which form instance to load the data into
procedure TTest.LoadMyData(ALoadForm: TForm1);
begin
ALoadForm.LoadData;
end;
//Change FormShow to tell Test which form to use in LoadMyData
procedure TForm1.FormShow(Sender: TObject);
begin
Initilize;
Test := TTest.Create;
Test.LoadMyData(Self);
end;
郑重声明,David 已经在 中向您提供了此信息。他的回答还展示了新 TTest
实例的适当资源保护,而为了简单起见,我将其省略。
By the way, these 2 little changes would have also solved your problem.
Basically, you had 2 mistakes in your code. The combination of both mistakes caused your problem.
You can fix either one to make the problem go away. But you should fix both to make your code better.
我有一个表单 TForm1,我在其中保留了 cxSpreadsheet 组件。我还创建了一个名为 TTest 的 class。 TTest class 包含两种方法,一种用于加载数据,另一种用于保存数据。
当我在 TTest class 方法中调用 TForm1 class 的方法时,即使创建了组件,当我调用 Test.LoadMyData 方法时它也会出现访问冲突,它给 ATableView1 变量带来访问冲突。
我做错了什么?
type
TForm1 = class(TForm)
dxSpreadSheet1: TdxSpreadSheet;
procedure FormShow(Sender: TObject);
public
ATableView1 : TdxSpreadSheetTableView;
procedure Initilize;
procedure LoadData;
end;
TTest = class
public
procedure SaveMyData(MyValue : String);
procedure LoadMyData;
end;
var
Form1: TForm1;
Test: TTest;
implementation
{$R *.dfm}
procedure TForm1.FormShow(Sender: TObject);
begin
Form1 := TForm1.Create(Self);
Initilize;
Test := TTest.Create;
Test.LoadMyData;
end;
procedure TForm1.Initilize;
begin
ATableView1 := dxSpreadSheet1.Sheets[0] as TdxSpreadSheetTableView;
end;
procedure TForm1.LoadData;
begin
ATableView1.Cells[10,1].SetText('Test Application');
end;
procedure TTest.LoadMyData;
begin
Form1.LoadData;
end;
你搞得一团糟。 Delphi 方法对隐式 Self
对象(实例)进行操作。也称为方法调用的目标。如果我们用明确的 Self
编写您的代码,那么它看起来像这样:
procedure TForm1.FormShow(Sender: TObject);
begin
Form1 := TForm1.Create(Self);
Self.Initilize;
Test := TTest.Create;
Test.LoadMyData;
end;
procedure TForm1.Initilize;
begin
Self.ATableView1 := Self.dxSpreadSheet1.Sheets[0] as TdxSpreadSheetTableView;
end;
procedure TForm1.LoadData;
begin
Self.ATableView1.Cells[10,1].SetText('Test Application');
end;
procedure TTest.LoadMyData;
begin
Form1.LoadData;
end;
如您所见,您对 Self
和 Form1
的引用相当邪恶。请注意,Form1
全局变量是在 .dpr 文件中使用以下代码行实例化的:
Application.CreateForm(TForm1, Form1);
这最终导致 FormShow
在 Form1
上被调用。然后您覆盖 Form1
,现在有两个 TForm1
实例。您在 Self
上调用了 Initilize
,但随后在 Form1
上调用了 LoadData
。
整个事情一团糟。代码可能如下所示:
// remove global variable Test
procedure TForm1.FormShow(Sender: TObject);
var
Test: TTest;
begin
Initilize;
Test := TTest.Create;
try
Test.LoadMyData(Self);
finally
Test.Free;
end;
end;
procedure TForm1.Initilize;
begin
ATableView1 := dxSpreadSheet1.Sheets[0] as TdxSpreadSheetTableView;
end;
procedure TForm1.LoadData;
begin
ATableView1.Cells[10,1].SetText('Test Application');
end;
procedure TTest.LoadMyData(Form: TForm1);
begin
Form.LoadData;
end;
我们不使用全局变量,而是将表单引用作为参数传递。
一旦你掌握了这一点,你可能会考虑完全删除全局变量 Form1
。 IDE 在假设每个表单都只有一个实例的情况下创建此变量。但没有理由必须如此。就个人而言,我认为通过删除这些全局变量,您将在长期 运行 中受益。
正如 David 雄辩地指出的那样,这是你造成的混乱局面。他说 IDE 在项目文件中插入一行自动为您创建表单是正确的。所以自己创建它是多余的。
在此实例中,OnShow 处理程序可能是将一些测试数据添加到网格的正确位置,但我不会费心使用 TTest class 来执行此操作。也就是说,首先让逻辑正常工作,然后根据需要将内容移至另一个 class。我建议这样做是因为您似乎不太了解如何进行 OOD。我们都在那里!先把事情简单化。
我为 CodeRage 9 制作了一个您可能会感兴趣的视频。它叫做 "Have You Embraced Your Inner Software Plumber Lately?" 在 YouTube 上搜索它。它会让您对这个一般主题有一些了解(可能太多了)。
type
TForm1 = class(TForm)
dxSpreadSheet1: TdxSpreadSheet;
procedure FormShow(Sender: TObject);
public
ATableView1 : TdxSpreadSheetTableView;
procedure Initilize;
procedure LoadData;
end;
TTest = class
public
procedure SaveMyData(MyValue : String);
procedure LoadMyData;
end;
var
Form1: TForm1;
Test: TTest;
implementation
{$R *.dfm}
procedure TForm1.FormShow(Sender: TObject);
begin
//Form1 is nil, if I check before executing below statement.
Form1 := Self; //This statement make my code work. mmmhhhhhaaaa Love This Statement.
Initilize;
Test := TTest.Create;
Test.LoadMyData;
end;
在您的问题中,您遗漏了一个非常重要的难题。你在评论中提到过它,但我在这里重复一遍,因为它是你问题的直接触发因素。在评论中,您说表单的创建方式如下:
with TForm1.Create(Self) do
begin
try
ShowModal;
finally
Free;
end;
end;
您的问题是,在 ShowModal
的调用链中,您希望 Form1
被分配 专门给您刚刚创建的实例 。但显然你还没有做任何设置 Form1
.
您的 "solution" 是在 ShowModal
调用链中分配 Form1
。虽然这解决了您眼前的问题,但它远非正确,稍后我将解释原因。首先,我将展示可以完全避免该问题的简单解决方案(请注意,这不是一个完整的解决方案,因为您的代码存在的问题比您意识到的要多得多)。
解决办法,去掉和:
//This first line is the most important.
//It explicitly sets which variable must be assigned to the new form.
Form1 := TForm1.Create(Self);
try
Form1.ShowModal;
finally
Form1.Free;
end;
Vishal,试试吧。如果你这样做,你会看到它有效。希望我现在得到你的全部关注。起初你不相信,但也许你现在意识到我真的做理解正确.
那我为什么说你的 "solution" 错了呢?毕竟好像解决了问题....
Well, you said it yourself: you expected
Form1
to be assigned at the start ofFormShow
. You're right, it should be. By assigningForm1 := Self;
insideFormShow
you're just patching up the earlier mistake. Surely you agree it's better to fix the original mistake than to just patch over it later?
但是这里有一个更深层次的问题......我不确定你是否理解 "object instances" 和 "classes" 之间的区别。 (如果你这样做,那么请继续阅读接下来的几段以进行修改。)
您似乎希望在创建 TForm1
时自动将其分配给 Form1
变量。就好像您预期在任何时间点内存中只会有一个 TForm1
。但是 TForm1
是 class 类型;这意味着它通常定义 任意数量的相同类型的 对象实例的行为。每次您创建一个 TForm1
,它都是一个新的单独的表单实例。每个实例都可以有自己的变量分配给它。例如
JohnsForm1 := TForm1.Create(Self);
PaulsForm1 := TForm1.Create(Self);
考虑一下,如果您需要 2 个表单变量,您不正确的 "solution" 会发生什么情况?你会在 FormShow
方法中写什么? JohnsForm1 := Self;
或PaulsForm1 := Self;
?
当然你仍然可以选择一次只在内存中保留TForm1
的1个实例。但是 Delphi 无法自动知道这就是您的意图。所以你仍然应该像上面那样明确地做任何想要的任务。
我提到你的代码还有更严重的问题。这与上面关于对象实例和 class 类型的讨论有关。
你的TTest
class做了一堆不必要的假设:
- 它假定永远只有 1 个
TForm1
实例。 - 它假定表单在需要时始终可用。
- 并且它假定表单将分配给
Form1
变量。
所以,如果您需要 JohnsForm1
和 PaulsForm1
,您的代码将无法正常工作。
对 TTest.LoadMyData
和 TForm1.FormShow
的微小改动解决了这些问题。
//Write LoadMyData so it can be told which form instance to load the data into
procedure TTest.LoadMyData(ALoadForm: TForm1);
begin
ALoadForm.LoadData;
end;
//Change FormShow to tell Test which form to use in LoadMyData
procedure TForm1.FormShow(Sender: TObject);
begin
Initilize;
Test := TTest.Create;
Test.LoadMyData(Self);
end;
郑重声明,David 已经在 TTest
实例的适当资源保护,而为了简单起见,我将其省略。
By the way, these 2 little changes would have also solved your problem.
Basically, you had 2 mistakes in your code. The combination of both mistakes caused your problem.
You can fix either one to make the problem go away. But you should fix both to make your code better.