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

时间:2017-05-08 19:16:02

标签: forms delphi access-violation

要点: 一个表单(Loan Form)动态创建一个名为DatePickerForm的模态表单(当用户单击特定按钮时)。 在DatePickerForm中选择日期后,用户点击该表单'关闭' button:(a BitBtn) - 这是导致访问冲突错误的原因。

详细信息:

可重用模式DatePickerForm的目的是为用户提供在特殊情况下输入日期的一致方法。它将用于多种其他情况 - 也就是说,如果我按计划运行它。

确切的错误文本是:"项目ABCD.exe引发异常类$ C0000005,并在0x0060d0b1处发出消息'访问冲突:读取地址0x00000000'。"

代码编译,程序正常工作,直到下面的步骤4:

运行时过程:

  1. 用户点击贷款表单上的按钮(工作
  2. 创建模式形式DatePickerForm(所有者:应用程序),然后显示。 (的工作原理
  3. 用户从DatePicker控件中选择日期。 (的工作原理
  4. 用户点击确定按钮(失败
  5. DatePickerForm应该关闭,我们应该返回Loan表单 - 但错误发生了。
  6. 下一步是读取DatePicker表格DatePicker控件上的日期(表格仍然存在,此时此刻不可见)
  7. 我的问题

    A)这应该有效还是我使用动态表单创建不正确?

    B)有没有更好的方法来实现这一目标?

    任何帮助将不胜感激。

    约翰

    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.
    

    贷款表格 - 部分代码(完整代码约为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.
    

2 个答案:

答案 0 :(得分:2)

documentation说:

  

不要在您的应用程序中调用CloseModal。当需要关闭模态表单时,VCL使用CloseModal。 CloseModal不会自动关闭表单;它只是调用已注册的close事件并更新ModalResult属性。

所以,就像它说的那样。通过设置表单的ModalResult属性来关闭模式表单。

最简单的方法是删除按钮OnClick事件处理程序。而是在设计器中设置按钮的ModalResult属性。

答案 1 :(得分:0)

从错误消息中可以清楚地看到您正在访问nil指针。原因是你在一个全局CloseModal()对象指针上调用fmDatePicker_Popup(你不应该直接调用它),该指针实际上并没有指向一个有效的Form对象来开始用:

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

fmDatePicker_Popup nil的原因是因为在btnSetDefaultClick()中,当您创建TfmDatePicker_Popup对象时,您将其分配给不同的DatePickerForm变量fmDatePicker_Popup变量:

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

TfmDatePicker_Popup根本不应该依赖任何外部指针。由于btnOKClick()TfmDatePicker_Popup类的成员,因此它应该使用隐式Self指针:

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

或者简单地说:

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

话虽如此,CloseModal()无论如何都是错误的。它实际上并没有关闭表单,只是触发了Form OnClose事件。根据{{​​3}}文档:

  

要关闭模式表单,将其ModalResult属性设置为非零值

ShowModal()在检测到CloseModal()已变为非零时在内部调用ModalResult。如果OnClose事件处理程序将其Action参数设置为caNone,则ModalResult将重置为0并且表单未关闭。

因此,请使用表单的ModalResult属性,就像文档中所说:

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

然后可以通过完全删除OnClick处理程序并将按钮的ModalResult属性设置为非零值(或者在{{1}的情况下)来实现自动化},设置其TBitBtn属性,该属性也设置其Kind)。当点击模态表单上的按钮时,它会在触发其ModalResult事件之前将其自己的ModalResult指定给其父表单ModalResult

然后,您还应该将OnClick更改为更像这样:

btnSetDefaultClick()