【问题标题】:Delphi (Seattle) - Closing a dynamically created modal form causes access violationDelphi (Seattle) - 关闭动态创建的模式表单会导致访问冲突
【发布时间】:2017-05-08 19:16:02
【问题描述】:

总结: 一个表单(贷款表单)动态创建一个名为 DatePickerForm 的模态表单(当用户单击特定按钮时)。 在 DatePickerForm 中选择日期后,用户单击该表单的“关闭”按钮:(BitBtn) - 这就是导致访问冲突错误的原因。

详情:

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

确切的错误文本是:“项目 ABCD.exe 引发异常类 $C0000005 并带有消息‘访问冲突在 0x0060d0b1:读取地址 0x00000000’。”

代码编译,程序运行良好,直到下面的第 4 步:

运行时进程:

  1. 用户点击贷款表单上的按钮(works
  2. 创建模态表单 DatePickerForm(所有者:Application),然后显示。 (有效
  3. 用户从 DatePicker 控件中选择一个日期。 (有效
  4. 用户点击确定按钮(失败
  5. DatePickerForm 应该关闭,我们应该返回 Loan 表单 - 但发生了错误。
  6. 下一步将读取仍在 DatePicker 的表单 DatePicker 控件上的日期(该表单仍然存在,此时它只是不可见)

我的问题

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.

【问题讨论】:

    标签: forms delphi access-violation


    【解决方案1】:

    documentation 说:

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

    所以,照它说的做。通过设置表单的ModalResult 属性来关闭模式表单。

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

    【讨论】:

    • 感谢大卫 - 做到了!!!!我肯定要进一步研究这个(我讨厌不理解事情是如何运作的)。谢谢 - 约翰
    【解决方案2】:

    从错误消息中可以清楚地看出您正在访问nil 指针。这样做的原因是因为您在全局 fmDatePicker_Popup 对象指针上调用 CloseModal() (您不应该首先直接调用它)实际上并未指向有效的 Form 对象:

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

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

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

    TfmDatePicker_Popup 根本不应该依赖 any 指向自身的外部指针。由于btnOKClick()TfmDatePicker_Popup 类的成员,它应该使用隐式Self 指针:

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

    或者简单地说:

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

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

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

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

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

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

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

    然后,您还应该将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;
    

    【讨论】:

    • 雷米 - 感谢您的解释。这正是我需要理解的(我将不得不花一些时间思考和试验,然后才能完全确定我做对了,我觉得使用它很舒服)
    猜你喜欢
    • 1970-01-01
    • 2023-03-12
    • 1970-01-01
    • 1970-01-01
    • 2010-11-19
    • 1970-01-01
    • 1970-01-01
    • 2014-06-05
    • 1970-01-01
    相关资源
    最近更新 更多