Как изящно выйти из формы MDI, код которой выполняется в Delphi? - PullRequest
0 голосов
/ 26 января 2009

У меня есть приложение MDI, написанное на Delphi 2007.

Если пользователь выходит из формы внутри нее во время выполнения кода, это вызывает исключение, поскольку код пытается обновить компонент или использовать объект, который был освобожден с помощью формы.

Могу ли я в любом случае сказать, выполняется ли код в событии exit или есть стандартный способ справиться с этой ситуацией?

Обновление с дополнительной информацией

Исключение обычно происходит при следующих обстоятельствах.

Нажата кнопка на дочерней 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, какой-то глобальный способ сделать это будет идеальным, поэтому я могу добавить код в базовую форму и заставить его работать на всех дочерних формах.

Ответы [ 5 ]

4 голосов
/ 26 января 2009

Вы предоставляете недостаточно информации, но самое простое решение, которое приходит на ум, - это проверить в обработчике OnCloseQuery, выполняется ли код, и если это так, установите CanClose в False.

В качестве альтернативы вы можете отделить код от формы MDI, создав промежуточный объект, о котором знают как форма, так и фоновый код. Вы позволяете этому иметь ссылку на форму, которая сбрасывается при закрытии формы. Направляя весь доступ к форме через этот промежуточный объект, вы можете предотвратить исключения.

Редактировать: Вам необходимо предоставить информацию о том, как вы выполняете код, который пытается получить доступ к форме MDI после ее освобождения. Есть несколько способов выполнить рабочий код, например:

  • в методе формы или другого объекта
  • в обработчике события OnTimer
  • в обработчике OnIdle объекта Application
  • в фоновом потоке

Обратите внимание, что в первом случае форма может быть освобождена только в том случае, если вы делаете это самостоятельно в коде или вызываете Application.ProcessMessages. Без дополнительной информации о том, как выглядит ваш код, никто не сможет дать вам конкретный ответ на ваш вопрос.

Edit 2: С вашей добавленной информацией кажется, что рассматриваемый код всегда выполняется в методах формы. Это легко уловить, создав логический элемент, который имеет значение True, когда выполнение начинается, и значение False, когда выполнение завершается. Теперь вам нужно только добавить обработчик для OnCloseQuery в ваш базовый класс и установить для CanClose значение False, если для члена (например, fExecuting) задано значение True. Вы можете молча запретить закрытие или показать информационное окно. Я бы просто показывал форму прогресса или отображал что-то в строке состояния, чтобы не слишком прерывать пользователя с помощью модальных информационных блоков.

Что бы я определенно сделал, это позволил бы пользователю отменить длительный процесс. Таким образом, вы также можете показать окно сообщения с вопросом, хотят ли они отменить операцию и закрыть ее. Вам все еще нужно пропустить закрытие формы, но вы можете сохранить запрос на закрытие и обработать его после завершения выполнения.

0 голосов
/ 26 января 2009

Если пользователь хочет сдаться, потому что операция длится так долго, почему бы и им не разрешить? Немного измените свой код, чтобы проверить (прямо перед тем, как сообщения application.process подходящее место) переменную «хочет выйти», и, когда это так, выйти из цикла, освободить объекты и отменить. Затем оберните это в то, что dmajkic предложил ранее.

0 голосов
/ 26 января 2009

Я создал объект, который может выполнять процедуру или метод для вас без использования потока. Он использует таймер, но предоставляет только простой вызов в одну строку. Он также поддерживает RTTI, поэтому вы можете просто нажать кнопку:

ExecuteMethodProc (MyCode) или же ExecuteMethodName ('MyCode');

С уважением, Brian

// 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
//-----------------------------------------------------------------------------
0 голосов
/ 26 января 2009

Введите личное поле в форме MDI, например. FProcessing

в коде вызова db:

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

in MDIForm.FormCloseQuery () do

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;

Вы также должны проверить завершение всего приложения.

0 голосов
/ 26 января 2009

Каждая форма имеет событие OnCloseQuery.

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

Вы можете использовать это, чтобы отложить закрытие, установив CanClose в False.

Вам нужно решить, хотите ли вы обрабатывать закрытие до завершения обработки. Или вам может потребоваться, чтобы пользователь снова закрылся.

...