0

我有一个用 Delphi 2007 编写的 MDI 应用程序。

如果用户在执行代码时退出其中的表单,则会导致异常,因为代码正在尝试更新组件或使用已随表单释放的对象。

无论如何我可以判断代码是否在退出事件中执行,或者是否有处理这种情况的标准方法?

更新更多信息

异常通常发生在以下情况。

子 mdi 表单上的一个按钮被按下,这将激活表单中的一个功能,该功能将进入数据库并检索数据,然后它将重新格式化并将其显示在表单上的可视组件中(可用 TListView )。

如果代码需要很长时间执行(例如,如果有很多数据要处理),用户将失去兴趣并单击关闭按钮(代码的速度正在努力避免这种情况)。

即使它所属的表单已被释放,函数内部的代码仍在执行(代码位于表单的私有部分),现在当它尝试更新它们不再存在的可视组件时(因为它们被释放了表单),它会引发异常。

发生这种情况时,子表单中的代码可以在循环中使用,循环记录并相应地更新列表视图,循环包含看起来像这样的代码

inc(i);
if (i mod 25) = 0 then
begin
    StatusPnl.Caption := 'Loading ' + intToStr(i) + ', Please wait';
    application.ProcessMessages;
end;

其他代码示例

fromClose 事件看起来像这样

//Snip
if (Not (Owner = nil)) then
with (Owner as  IMainForm)do
begin
    //Snip
    DoFormFree(Self,Self.Name);
end
else
//Snip

DoFormFree 是主 mdi 父窗体中的一个函数,看起来像这样

//Snip
(G_FormList.Objects[x] as TBaseForm).Release;
G_FormList.Objects[i] := nil;
G_FormList.Delete(i);
//Snip

由于各种原因,所有表单都存储在一个列表中,并且所有子表单都扩展了 TBaseForm 类。

理想情况下,我想要一种方法来判断表单中的代码是否正在执行,并防止用户关闭表单,或者在代码完成之前隐藏它,因为在某些情况下,它可能会生成报告并更新为状态面板发生异常,在这种情况下,报告将不完整。

因为所有表单都是 TbaseFrom 的子类,所以某种全局方式这样做是理想的,所以我可以将代码添加到基本表单并让它适用于所有下降的表单。

4

5 回答 5

4

您提供的信息不足,但想到的最简单的解决方案是在 OnCloseQuery 处理程序中测试代码是否正在执行,如果是,请将 CanClose 设置为 False。

或者,您可以通过创建表单和后台代码都知道的中间对象来将代码与 MDI 表单分离。你让它有一个对表单的引用,当表单关闭时它会被重置。通过此中间对象路由对表单的所有访问,您可以防止异常。

编辑:您需要提供有关在释放 MDI 表单后如何执行尝试访问 MDI 表单的代码的信息。有一些方法可以执行工作代码,例如:

  • 在形式或另一个对象的方法中
  • 在 OnTimer 事件处理程序中
  • 在 Application 对象的 OnIdle 处理程序中
  • 在后台线程中

请注意,在第一种情况下,只有在您自己在代码中执行此操作或调用 Application.ProcessMessages 时才能释放表单。如果没有有关您的代码外观的更多信息,没有人可以为您的问题提供具体答案。

编辑2:根据您添加的信息,有问题的代码似乎总是在表单的方法中执行。这很容易通过创建一个布尔成员来捕获,该成员在执行开始时设置为 True,在执行完成时设置为 False。现在您只需在基类中为 OnCloseQuery 添加一个处理程序,如果成员(例如 fExecuting)为 True,则将 CanClose 设置为 False。您可以静默禁止关闭,或显示信息框。我只是在状态栏中显示进度表或显示某些内容,以免使用模式信息框过多地打扰用户。

我肯定会做的是允许用户取消长时间运行的过程。因此,您还可以显示一个消息框,询问用户是否要取消操作并关闭。您仍然需要跳过关闭表单,但可以存储关闭请求,并在执行结束后处理它。

于 2009-01-26T09:30:45.587 回答
0

每个表单都有一个 OnCloseQuery 事件。

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);

您可以通过将 CanClose 设置为 False 来使用它来推迟关闭。

您需要决定是否要在处理完成之前处理关闭。或者您可能要求用户再次关闭。

于 2009-01-26T09:30:06.443 回答
0

以 MDI 形式引入私有字段,例如。F加工

在数据库调用代码中做:

FProcessing := true;
try
  i := 0;  
  if (i mod 25) = 0 then
  begin
    // do your code 
    Application.ProcessMessages; 
  end;
finally
  FProcessing := false; 
end;

MDIForm.FormCloseQuery()

procedure TMDIForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  if FProcesing then 
    CanClose := False;  
   // or you can ask user to stop fetching db data
end;

您还应该检查整个应用程序终止。

于 2009-01-26T11:24:39.553 回答
0

我创建了一个对象,它可以在不使用线程的情况下为您执行过程或方法。它使用一个计时器,但只公开一个简单的单行调用。它还支持 RTTI,因此您只需单击按钮即可:

ExecuteMethodProc( MyCode ) 或 ExecuteMethodName( 'MyCode' );

问候,布赖恩

// Method execution
//-----------------------------------------------------------------------------

type
  TArtMethodExecuter = class( TObject )
    constructor Create;
    destructor  Destroy; override;
  PRIVATE

    FMethod           : TProcedureOfObject;
    FTimer            : TTimer;
    FBusy             : boolean;
    FFreeAfterExecute : boolean;
    FHandleExceptions : boolean;

    procedure DoOnTimer( Sender : TObject );
    procedure SetBusy( AState : boolean );

  PUBLIC
    procedure ExecuteMethodProc(
                AMethod       : TProcedureOfObject;
                AWait         : boolean = False );

    procedure ExecuteMethodName(
                AMethodObject : TObject;
          const AMethodName   : string;
                AWait         : boolean = False );

    property  FreeAfterExecute : boolean
                read FFreeAFterExecute
                write FFreeAfterExecute;

    property  HandleExceptions : boolean
                read FHandleExceptions
                write FHandleExceptions;

    property  Busy : boolean
                read FBusy;

  end;





procedure ExecuteMethodName(
            AMethodObject : TObject;
     const  AMethodName    : string;
            AHandleExceptions : boolean = True );
// Executes this method of this object in the context of the application.
// Returns immediately, with the method executing shortly.

procedure ExecuteMethodProc(
            AMethodProc : TProcedureOfObject;
            AHandleExceptions : boolean = True );
// Executes this method of this object in the context of the application.
// Returns immediately, with the method executing shortly.

function  IsExecutingMethod : boolean;
// Returns TRUE if we are already executing a method.


// End method execution
//-----------------------------------------------------------------------------




// Method execution
//-----------------------------------------------------------------------------


{ TArtMethodExecuter }

var
  iMethodsExecutingCount : integer = 0;

const
  wm_ExecuteMethod = wm_User;

constructor TArtMethodExecuter.Create;
begin
  Inherited;
end;

destructor TArtMethodExecuter.Destroy;
begin
  FreeAndNil( FTimer );
  Inherited;
end;

procedure TArtMethodExecuter.DoOnTimer( Sender : TObject );

  procedure RunMethod;
  begin
    try
      FMethod
    except
      on E:Exception do
        ArtShowMessage( E.Message );
    end
  end;

begin
  FreeAndNil(FTimer);
  try
    If Assigned( FMethod ) then
      RunMethod
     else
      Raise EArtLibrary.Create(
        'Cannot execute method - no method defined.' );
  finally
    SetBusy( False );
    If FFreeAfterExecute then
      Free;
  end;
end;



procedure TArtMethodExecuter.SetBusy(AState: boolean);
begin
  FBusy := AState;

  If AState then
    Inc( iMethodsExecutingCount )
   else
    If iMethodsExecutingCount > 0 then
      Dec( iMethodsExecutingCount )
end;



procedure TArtMethodExecuter.ExecuteMethodProc(
          AMethod       : TProcedureOfObject;
          AWait         : boolean = False );
begin
  SetBusy( True );
  FMethod         := AMethod;
  FTimer          := TTimer.Create( nil );
  FTimer.OnTimer  := DoOnTimer;
  FTimer.Interval := 1;
  If AWait then
    While FBusy do
      begin
      Sleep( 100 );
      Application.ProcessMessages;
      end;
end;



procedure TArtMethodExecuter.ExecuteMethodName(AMethodObject: TObject;
  const AMethodName: string; AWait: boolean);
var
  RunMethod : TMethod;
begin
  RunMethod.code := AMethodObject.MethodAddress( AMethodName );
  If not Assigned( RunMethod.Code ) then
    Raise EArtLibrary.CreateFmt(
      'Cannot find method name "%s". Check that it is defined and published.', [AMethodName] );

  RunMethod.Data := AMethodObject;
  If not Assigned( RunMethod.Data ) then
    Raise EArtLibrary.CreateFmt(
      'Method object associated with method name "%s" is not defined.', [AMethodName] );

  ExecuteMethodProc(
    TProcedureOfObject( RunMethod ),
    AWait );
end;


procedure ExecuteMethodName(
            AMethodObject : TObject;
      const AMethodName   : string;
            AHandleExceptions : boolean = True );
// Executes this method of this object in the context of the application.
// Returns immediately, with the method executing shortly.
var
  ME : TArtMethodExecuter;
begin
  If IsExecutingMethod then
    If AHandleExceptions then
      begin
      ArtShowMessage( 'A method is already executing.' );
      Exit;
      end
     else
      Raise EArtLibrary.Create( 'A method is already executing.' );

  ME := TArtMethodExecuter.Create;
  ME.FreeAfterExecute := True;
  ME.HandleExceptions := AHandleExceptions;
  ME.ExecuteMethodName( AMethodObject, AMethodName );
end;


procedure ExecuteMethodProc(
            AMethodProc : TProcedureOfObject;
            AHandleExceptions : boolean = True );
// Executes this method of this object in the context of the application.
// Returns immediately, with the method executing shortly.
var
  ME : TArtMethodExecuter;
begin
  If IsExecutingMethod then
    If AHandleExceptions then
      begin
      ArtShowMessage( 'A method is already executing.' );
      Exit;
      end
     else
      Raise EArtLibrary.Create( 'A method is already executing.' );

  ME := TArtMethodExecuter.Create;
  ME.FreeAfterExecute := True;
  ME.HandleExceptions := AHandleExceptions;
  ME.ExecuteMethodProc( AMethodProc );
end;

function  IsExecutingMethod : boolean;
// Returns TRUE if we are already executing a method.
begin
  Result := iMethodsExecutingCount > 0;
end;

// End Method execution
//-----------------------------------------------------------------------------
于 2009-01-26T12:00:21.320 回答
0

如果用户因为操作时间太长而想放弃,他们为什么不让他们呢?稍微修改您的代码以检查(在 application.process 消息是一个好地方之前)“想要退出”变量,当它为真时,从循环中退出,释放您的对象并取消。然后将其包装在 dmajkic 之前建议的内容中。

于 2009-01-26T19:42:44.587 回答