简体   繁体   English

Delphi(西雅图)-关闭动态创建的模式形式会导致访问冲突

[英]Delphi (Seattle) - Closing a dynamically created modal form causes access violation

Summary: one form (Loan Form) dynamically creates a modal form called DatePickerForm (when user clicks a specific button). 简介:一种表单(贷款表单)可动态创建一个名为DatePickerForm的模式表单(当用户单击特定按钮时)。 After selecting a date in the DatePickerForm, the user clicks on that form's 'Close' button: (a BitBtn) - this is what causes an access violation error. 在DatePickerForm中选择一个日期后,用户单击该表单的“关闭”按钮:(一个BitBtn)-这是导致访问冲突错误的原因。

Details: 细节:

The purpose of the reusable modal DatePickerForm is to provide users with a consistent way of entering dates in special circumstances. 可重用的模式DatePickerForm的目的是为用户提供在特殊情况下输入日期的一致方法。 It will be used in multiple other situations - that is, if I get it to work as planned. 它会在其他多种情况下使用-也就是说,如果我能够按计划工作。

Exact error text is: "Project ABCD.exe raised exception class $C0000005 with message 'access violation at 0x0060d0b1: read of address 0x00000000'." 确切的错误文本是:“ Project ABCD.exe引发了异常类$ C0000005,消息为“在0x0060d0b1发生访问冲突:读取地址0x00000000”。

The code compiles and the program works fine until step 4 below: 代码会编译,并且程序可以正常运行,直到执行下面的第4步:

Run-time Process: 运行时过程:

  1. The user clicks on a button on the Loan form ( works ) 用户单击“贷款”表单上的按钮( 有效
  2. The modal form DatePickerForm is created (owner: Application), then shown. 创建模式形式的DatePickerForm(所有者:Application),然后显示。 ( works ) 作品
  3. The user selects a date from the DatePicker control. 用户从DatePicker控件中选择一个日期。 ( works ) 作品
  4. The User clicks on the OK button ( fails ) 用户单击“确定”按钮( 失败
  5. The DatePickerForm should close and we should return to the Loan form - but the error occurs instead. DatePickerForm应该关闭,我们应该返回到Loan表单-但是会出现错误。
  6. The next step would be reading the date still on the DatePicker's form DatePicker control (the form still exists, it is just invisible at this point) 下一步将是仍然在DatePicker的窗体DatePicker控件上读取日期(该窗体仍然存在,此时它是不可见的)

My questions : 我的问题

A) Should this work or am I using dynamic form creation incorrectly? A)应该这样做还是我使用动态表单创建不正确?

B) Is there a better way to achieve this? B)是否有更好的方法来实现这一目标?

Any help will be appreciated. 任何帮助将不胜感激。

John 约翰

DatePickerForm code (complete): DatePickerForm代码(完整):

unit DatePicker_PopupForm;

interface

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

type
  TfmDatePicker_Popup = class(TForm)
      DTDatePicker: TDateTimePicker;
      lblDatePrompt: TLabel;
      btnOK: TBitBtn;
      procedure btnOKClick(Sender: TObject);
  private
      { Private declarations }
  public
      { Public declarations }
  end;

var
    fmDatePicker_Popup: TfmDatePicker_Popup;

implementation

{$R *.dfm}

procedure TfmDatePicker_Popup.btnOKClick(Sender: TObject);
begin
    fmDatePicker_Popup.CloseModal;
end;
end.

Loan form - partial code (complete code is roughly 9700 lines long) 贷款形式-部分代码(完整代码长约9700行)

unit LoanForm;

    interface

    uses
      Winapi.Windows, ......, DatePicker_PopupForm;

    ...

    implementation

    ...

    procedure TfmLoan.btnSetDefaultClick(Sender: TObject);
    begin
       DatePickerForm := TfmDatePicker_Popup.Create(Application);
       DatePickerForm.DTDatePicker.Date := GD_ProcessDate;
       DatePickerForm.ShowModal;        
       dDefaultDate := DatePickerForm.DTDatePicker.Date;
    end;
       ...

  end.

The documentation says: 文件说:

Do not call CloseModal in your application. 不要在您的应用程序中调用CloseModal。 CloseModal is used by the VCL when a modal form needs to be closed. 当需要关闭模式窗体时,VCL使用CloseModal。 CloseModal does not close the form by itself; CloseModal不会自行关闭表单; it simply calls the registered close events and updates the ModalResult property. 它仅调用已注册的关闭事件并更新ModalResult属性。

So, do as it says. 因此,按照说明进行操作。 Close a modal form by setting the form's ModalResult property. 通过设置窗体的ModalResult属性来关闭模式窗体。

The easiest way to do that is to remove the button OnClick event handler. 最简单的方法是删除按钮OnClick事件处理程序。 Instead set the button's ModalResult property in the designer. 而是在设计器中设置按钮的ModalResult属性。

It is clear from the error message that you are accessing a nil pointer. 从错误消息中很明显,您正在访问nil指针。 And the reason for that is because you are calling CloseModal() (which you should not be calling directly in the first place) on a global fmDatePicker_Popup object pointer that is not actually pointing at a valid Form object to begin with: 这样做的原因是因为您正在全局fmDatePicker_Popup对象指针上调用CloseModal() (首先不应直接调用它),而该指针实际上并未指向有效的Form对象开头:

procedure TfmDatePicker_Popup.btnOKClick(Sender: TObject);
begin
  fmDatePicker_Popup.CloseModal; // <-- fmDatePicker_Popup is not assigned!
end;

The reason fmDatePicker_Popup is nil is because in btnSetDefaultClick() , when you create your TfmDatePicker_Popup object, you are assigning it to a different DatePickerForm variable instead of the fmDatePicker_Popup variable: fmDatePicker_Popupnil的原因是,在btnSetDefaultClick() ,当您创建TfmDatePicker_Popup对象时,您将其分配给另一个DatePickerForm变量而不是fmDatePicker_Popup变量:

procedure TfmLoan.btnSetDefaultClick(Sender: TObject);
begin
  DatePickerForm := TfmDatePicker_Popup.Create(Application); // <--
  ...
end;

TfmDatePicker_Popup shouldn't be relying on any external pointers to itself at all. TfmDatePicker_Popup根本不应该依赖任何指向其自身的外部指针。 Since btnOKClick() is a member of the TfmDatePicker_Popup class, it should be using the implicit Self pointer instead: 由于btnOKClick()TfmDatePicker_Popup类的成员,因此应改为使用隐式的Self指针:

procedure TfmDatePicker_Popup.btnOKClick(Sender: TObject);
begin
  Self.CloseModal;
end;

Or simply: 或者简单地:

procedure TfmDatePicker_Popup.btnOKClick(Sender: TObject);
begin
  CloseModal;
end;

That being said, CloseModal() is the wrong thing to call anyway. 话虽如此, CloseModal()仍然是错误的调用。 It doesn't actually close the Form, it just triggers the Form's OnClose event. 它实际上并没有关闭Form,它只是触发Form的OnClose事件。 Per the ShowModal() documentation: 根据ShowModal()文档:

To close a modal form, set its ModalResult property to a nonzero value . 要关闭模式形式, 请将其ModalResult属性设置为非零值

ShowModal() internally calls CloseModal() when it detects the ModalResult has become non-zero. 当检测到ModalResult已变为非零时, ShowModal()内部调用CloseModal() If the OnClose event handler sets its Action parameter to caNone , the ModalResult is reset to 0 and the Form is not closed. 如果OnClose事件处理程序将其Action参数设置为caNone ,则ModalResult将重置为0,并且不会关闭Form。

So use the Form's ModalResult property instead, like the documentation says to: 因此,请改用Form的ModalResult属性,如文档所述:

procedure TfmDatePicker_Popup.btnOKClick(Sender: TObject);
begin
  Self.ModalResult := mrOk;
end;

Which can then be automated by removing the OnClick handler altogether and instead setting the button's ModalResult property to a non-zero value (or, in the case of TBitBtn , set its Kind property, which also sets its ModalResult ). 然后可以通过完全删除OnClick处理程序并将按钮的ModalResult属性设置为非零值来自动执行此操作(或者,对于TBitBtn ,请设置其Kind属性,该属性也将设置其ModalResult )。 When a button on a modal Form is clicked, it assigns its own ModalResult to its parent Form's ModalResult before triggering its OnClick event. 单击模式窗体上的按钮时,它将在触发其OnClick事件之前将其自己的ModalResult分配给其父窗体的ModalResult

And then, you should also change btnSetDefaultClick() to look more like this instead: 然后,您还应该更改btnSetDefaultClick()使其更像这样:

procedure TfmLoan.btnSetDefaultClick(Sender: TObject);
var
  DatePickerForm: TfmDatePicker_Popup;
begin
  DatePickerForm := TfmDatePicker_Popup.Create(nil);
  try
    DatePickerForm.DTDatePicker.Date := GD_ProcessDate;
    if DatePickerForm.ShowModal = mrOk then
      dDefaultDate := DatePickerForm.DTDatePicker.Date;
  finally
    DatePickerForm.Free;
  end;
end;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM